From b29d98d5a2cb055233db8be4c0a7b111be8a8efe Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 13 Jun 2018 13:12:44 +0200 Subject: [PATCH 001/124] add WorkManager arch component --- OpenKeychain/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 3e92222c4..e9de55ec9 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -60,6 +60,8 @@ dependencies { implementation project(':KeybaseLib') implementation project(':safeslinger-exchange') + implementation "android.arch.work:work-runtime:1.0.0-alpha02" + // Unit tests in the local JVM with Robolectric // https://developer.android.com/training/testing/unit-testing/local-unit-tests.html // http://robolectric.org/getting-started/ From 1593cc08266d50d1b798ac4fa917d2137a8209cc Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 13 Jun 2018 13:13:49 +0200 Subject: [PATCH 002/124] use WorkManager to schedule temporary file cleanups --- .../keychain/KeychainApplication.java | 8 +- .../provider/TemporaryFileProvider.java | 74 +++++++++++++------ 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java index c72a90a45..dba58fb00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java @@ -24,13 +24,8 @@ import java.util.HashMap; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; -import android.app.job.JobScheduler; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Handler; import android.support.annotation.Nullable; import android.widget.Toast; @@ -39,7 +34,6 @@ import org.sufficientlysecure.keychain.network.TlsCertificatePinning; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.util.PRNGFixes; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; @@ -114,7 +108,7 @@ public class KeychainApplication extends Application { TlsCertificatePinning.addPinnedCertificate("api.keybase.io", getAssets(), "api.keybase.io.CA.cer"); TlsCertificatePinning.addPinnedCertificate("keyserver.ubuntu.com", getAssets(), "DigiCertGlobalRootCA.cer"); - new Handler().postDelayed(() -> TemporaryFileProvider.cleanUp(getApplicationContext()), 1000); + TemporaryFileProvider.scheduleCleanupImmediately(); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java index 8a9ee7738..18ca44a0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java @@ -18,8 +18,17 @@ package org.sufficientlysecure.keychain.provider; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import android.content.ClipDescription; import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -29,18 +38,15 @@ import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; +import android.support.annotation.NonNull; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; +import androidx.work.Worker; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.DatabaseUtil; import timber.log.Timber; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to * sharing them with other applications. @@ -81,16 +87,20 @@ public class TemporaryFileProvider extends ContentProvider { private static Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+"); public static Uri createFile(Context context, String targetName, String mimeType) { + ContentResolver contentResolver = context.getContentResolver(); + ContentValues contentValues = new ContentValues(); contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName); contentValues.put(TemporaryFileColumns.COLUMN_TYPE, mimeType); - return context.getContentResolver().insert(CONTENT_URI, contentValues); + contentValues.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis()); + Uri resultUri = contentResolver.insert(CONTENT_URI, contentValues); + + scheduleCleanupAfterTtl(); + return resultUri; } public static Uri createFile(Context context, String targetName) { - ContentValues contentValues = new ContentValues(); - contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName); - return context.getContentResolver().insert(CONTENT_URI, contentValues); + return createFile(context, targetName, null); } public static Uri createFile(Context context) { @@ -110,15 +120,6 @@ public class TemporaryFileProvider extends ContentProvider { return context.getContentResolver().update(uri, values, null, null); } - public static int cleanUp(Context context) { - Timber.d("Cleaning up temporary files…"); - return context.getContentResolver().delete( - CONTENT_URI, - TemporaryFileColumns.COLUMN_TIME + "< ?", - new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)} - ); - } - private class TemporaryStorageDatabase extends SQLiteOpenHelper { public TemporaryStorageDatabase(Context context) { @@ -247,9 +248,6 @@ public class TemporaryFileProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues values) { - if (!values.containsKey(TemporaryFileColumns.COLUMN_TIME)) { - values.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis()); - } String uuid = UUID.randomUUID().toString(); values.put(TemporaryFileColumns.COLUMN_UUID, uuid); int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values); @@ -310,4 +308,34 @@ public class TemporaryFileProvider extends ContentProvider { return openFileHelper(uri, mode); } + public static void scheduleCleanupAfterTtl() { + OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class) + .setInitialDelay(Constants.TEMPFILE_TTL, TimeUnit.MILLISECONDS).build(); + WorkManager.getInstance().enqueue(cleanupWork); + } + + public static void scheduleCleanupImmediately() { + OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class).build(); + WorkManager workManager = WorkManager.getInstance(); + if (workManager != null) { // it's possible this is null, if this is called in onCreate of secondary processes + workManager.enqueue(cleanupWork); + } + } + + public static class CleanupWorker extends Worker { + @NonNull + @Override + public WorkerResult doWork() { + Timber.d("Cleaning up temporary files…"); + + ContentResolver contentResolver = getApplicationContext().getContentResolver(); + contentResolver.delete( + CONTENT_URI, + TemporaryFileColumns.COLUMN_TIME + " <= ?", + new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)} + ); + + return WorkerResult.SUCCESS; + } + } } From 08ab5669a54dd0fe8f9448625fb010109be9bd2c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 13 Jun 2018 13:35:57 +0200 Subject: [PATCH 003/124] extract getDrawableAsNotificationBitmap into ResourceUtils --- .../service/KeyserverSyncAdapterService.java | 30 ++----------------- .../keychain/util/ResourceUtils.java | 29 ++++++++++++++++++ 2 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ResourceUtils.java 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 2a763c105..6799b8d0e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -37,12 +37,8 @@ import android.content.Context; import android.content.Intent; 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; import android.os.IBinder; @@ -68,6 +64,7 @@ import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.ResourceUtils; import timber.log.Timber; @@ -509,7 +506,7 @@ public class KeyserverSyncAdapterService extends Service { private Notification getOrbotNoification(Context context) { NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) - .setLargeIcon(getBitmap(R.mipmap.ic_launcher, context)) + .setLargeIcon(ResourceUtils.getDrawableAsNotificationBitmap(context, R.mipmap.ic_launcher)) .setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title)) .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg)) .setAutoCancel(true); @@ -606,27 +603,4 @@ public class KeyserverSyncAdapterService extends Service { serviceIntent.setAction(ACTION_UPDATE_ALL); this.startService(serviceIntent); } - - // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android - private Bitmap getBitmap(int resId, Context context) { - int mLargeIconWidth = (int) context.getResources().getDimension( - android.R.dimen.notification_large_icon_width); - int mLargeIconHeight = (int) context.getResources().getDimension( - android.R.dimen.notification_large_icon_height); - Drawable d; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // noinspection deprecation (can't help it at this api level) - d = context.getResources().getDrawable(resId); - } else { - d = context.getDrawable(resId); - } - if (d == null) { - return null; - } - Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(b); - d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight); - d.draw(c); - return b; - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ResourceUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ResourceUtils.java new file mode 100644 index 000000000..b5432ff4e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ResourceUtils.java @@ -0,0 +1,29 @@ +package org.sufficientlysecure.keychain.util; + + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; + + +public class ResourceUtils { + public static Bitmap getDrawableAsNotificationBitmap(@NonNull Context context, @DrawableRes int iconRes) { + Drawable iconDrawable = ContextCompat.getDrawable(context, iconRes); + if (iconDrawable == null) { + return null; + } + Resources resources = context.getResources(); + int largeIconWidth = resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width); + int largeIconHeight = resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height); + Bitmap b = Bitmap.createBitmap(largeIconWidth, largeIconHeight, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + iconDrawable.setBounds(0, 0, largeIconWidth, largeIconHeight); + iconDrawable.draw(c); + return b; + } +} From 40b7701f58a7bcd062ac5c49f45945b88093234a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 13 Jun 2018 16:20:23 +0200 Subject: [PATCH 004/124] use WorkManager for key sync --- OpenKeychain/src/main/AndroidManifest.xml | 24 - .../keychain/Constants.java | 1 + .../keychain/KeychainApplication.java | 15 +- .../keysync/KeyserverSyncManager.java | 86 +++ .../keychain/keysync/KeyserverSyncWorker.java | 198 ++++++ .../keychain/network/NetworkReceiver.java | 70 -- .../keychain/provider/KeyRepository.java | 3 +- .../keychain/provider/KeychainContract.java | 1 + .../keychain/provider/KeychainProvider.java | 19 +- .../provider/LastUpdateInteractor.java | 29 +- .../service/KeyserverSyncAdapterService.java | 606 ------------------ .../ui/OrbotRequiredDialogActivity.java | 39 ++ .../keychain/ui/SettingsActivity.java | 38 +- .../keychain/ui/base/BaseActivity.java | 2 - .../keychain/ui/util/KeyFormattingUtils.java | 5 + .../keychain/util/Preferences.java | 48 +- .../src/main/res/xml/sync_preferences.xml | 3 +- 17 files changed, 427 insertions(+), 760 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncManager.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/NetworkReceiver.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 20c5f550b..6bea7ff0a 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -95,15 +95,6 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/Theme.Keychain.Light"> - - - - - - @@ -1070,21 +1061,6 @@ android:resource="@xml/sync_adapter_contacts_structure" /> - - - - - - - - - . + */ + +package org.sufficientlysecure.keychain.keysync; + + +import java.util.concurrent.TimeUnit; + +import android.content.Context; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; + +import androidx.work.Constraints.Builder; +import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Preferences; +import timber.log.Timber; + + +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"; + + public static void updateKeyserverSyncSchedule(Context context, boolean forceReschedule) { + Preferences prefs = Preferences.getPreferences(context); + if (!forceReschedule && prefs.isKeyserverSyncScheduled() != prefs.isKeyserverSyncEnabled()) { + return; + } + WorkManager workManager = WorkManager.getInstance(); + if (workManager == null) { + Timber.e("WorkManager unavailable!"); + return; + } + workManager.cancelAllWorkByTag(WORK_TAG); + + if (!prefs.isKeyserverSyncEnabled()) { + return; + } + + Builder constraints = new Builder() + .setRequiredNetworkType(prefs.getWifiOnlySync() ? NetworkType.UNMETERED : NetworkType.CONNECTED) + .setRequiresBatteryNotLow(true); + if (VERSION.SDK_INT >= VERSION_CODES.M) { + constraints.setRequiresDeviceIdle(true); + } + + PeriodicWorkRequest workRequest = + new PeriodicWorkRequest.Builder(KeyserverSyncWorker.class, SYNC_INTERVAL, SYNC_INTERVAL_UNIT) + .setConstraints(constraints.build()) + .addTag(WORK_TAG) + .build(); + workManager.enqueue(workRequest); + + prefs.setKeyserverSyncScheduled(true); + } + + public static void runSyncNow() { + WorkManager workManager = WorkManager.getInstance(); + if (workManager == null) { + Timber.e("WorkManager unavailable!"); + return; + } + + 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 new file mode 100644 index 000000000..240389d83 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java @@ -0,0 +1,198 @@ +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.content.Context; +import android.support.annotation.NonNull; + +import androidx.work.Worker; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.operations.ImportOperation; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.provider.LastUpdateInteractor; +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 timber.log.Timber; + + +public class KeyserverSyncWorker extends Worker { + // 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 LastUpdateInteractor lastUpdateInteractor; + private KeyWritableRepository keyWritableRepository; + private Preferences preferences; + + @NonNull + @Override + public WorkerResult doWork() { + lastUpdateInteractor = LastUpdateInteractor.create(getApplicationContext()); + keyWritableRepository = KeyWritableRepository.create(getApplicationContext()); + preferences = Preferences.getPreferences(getApplicationContext()); + + Timber.d("Starting key sync…"); + ImportKeyResult result = updateKeysFromKeyserver(getApplicationContext()); + return handleUpdateResult(result); + } + + private ImportKeyResult updateKeysFromKeyserver(Context context) { + long staleKeyThreshold = System.currentTimeMillis() - 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()); + } + + // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync + CryptoInputParcel cryptoInputParcel = CryptoInputParcel.createCryptoInputParcel(); + if (preferences.getParcelableProxy().isTorEnabled()) { + return staggeredUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel); + } else { + return directUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel); + } + } + + 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) { + Timber.d("Starting normal update"); + ImportOperation importOp = new ImportOperation(context, keyWritableRepository, null); + 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 + * + * @param result + * result of keyserver sync + */ + private WorkerResult handleUpdateResult(ImportKeyResult result) { + if (result.isPending()) { + Timber.d("Orbot required for sync but not running, attempting to start"); + // result is pending due to Orbot not being started + // try to start it silently, if disabled show notifications + new OrbotHelper.SilentStartManager() { + @Override + protected void onOrbotStarted() { + } + + @Override + protected void onSilentStartDisabled() { + OrbotRequiredDialogActivity.showOrbotRequiredNotification(getApplicationContext()); + } + }.startOrbotAndListen(getApplicationContext(), false); + return WorkerResult.RETRY; + } else if (isStopped()) { + Timber.d("Keyserver sync cancelled"); + return WorkerResult.FAILURE; + } else { + Timber.d("Keyserver sync completed: Updated: %d, Failed: %d", result.mUpdatedKeys, result.mBadKeys); + return WorkerResult.SUCCESS; + } + } + + /** + * 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(); + } + + @Override + public void onStopped() { + super.onStopped(); + cancellationSignal.set(true); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/NetworkReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/NetworkReceiver.java deleted file mode 100644 index ec3cebf0f..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/NetworkReceiver.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.network; - -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.service.KeyserverSyncAdapterService; -import timber.log.Timber; - - -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 != null) && - (networkInfo.getType() == ConnectivityManager.TYPE_WIFI); - boolean isConnected = (networkInfo != null) && 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); - Timber.d("Wifi Receiver is enabled!"); - } else { - pm.setComponentEnabledSetting(compName, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - Timber.d("Wifi Receiver is disabled!"); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 2f1847a17..fce2c131c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import timber.log.Timber; @@ -284,7 +285,7 @@ public class KeyRepository { Cursor lastUpdatedCursor = contentResolver.query( UpdatedKeys.CONTENT_URI, new String[] { UpdatedKeys.LAST_UPDATED }, - UpdatedKeys.MASTER_KEY_ID + " = ?", + Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { "" + masterKeyId }, null ); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index b97bb44cc..396384dd7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -55,6 +55,7 @@ public class KeychainContract { String MASTER_KEY_ID = "master_key_id"; // not a database id String LAST_UPDATED = "last_updated"; // time since epoch in seconds String SEEN_ON_KEYSERVERS = "seen_on_keyservers"; + String FINGERPRINT = "fingerprint"; } interface KeySignaturesColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index d581de0f5..f1d2b3326 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -788,14 +788,23 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case UPDATED_KEYS: case UPDATED_KEYS_SPECIFIC: { HashMap projectionMap = new HashMap<>(); - qb.setTables(Tables.UPDATED_KEYS); - projectionMap.put(UpdatedKeys.MASTER_KEY_ID, Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID); - projectionMap.put(UpdatedKeys.LAST_UPDATED, Tables.UPDATED_KEYS + "." + UpdatedKeys.LAST_UPDATED); + projectionMap.put(UpdatedKeys.MASTER_KEY_ID, + Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " AS " + UpdatedKeys.MASTER_KEY_ID); + projectionMap.put(UpdatedKeys.LAST_UPDATED, + Tables.UPDATED_KEYS + "." + UpdatedKeys.LAST_UPDATED + " AS " + UpdatedKeys.LAST_UPDATED); projectionMap.put(UpdatedKeys.SEEN_ON_KEYSERVERS, - Tables.UPDATED_KEYS + "." + UpdatedKeys.SEEN_ON_KEYSERVERS); + Tables.UPDATED_KEYS + "." + UpdatedKeys.SEEN_ON_KEYSERVERS + " AS " + UpdatedKeys.SEEN_ON_KEYSERVERS); + projectionMap.put(UpdatedKeys.FINGERPRINT, + Tables.KEYS + "." + Keys.FINGERPRINT + " AS " + UpdatedKeys.FINGERPRINT); qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.UPDATED_KEYS + + " LEFT JOIN " + Tables.KEYS + + " ON (" + Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + ")" + ); + if (match == UPDATED_KEYS_SPECIFIC) { - qb.appendWhere(UpdatedKeys.MASTER_KEY_ID + " = "); + qb.appendWhere(Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java index 89520cb1a..268df323d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java @@ -1,7 +1,10 @@ package org.sufficientlysecure.keychain.provider; +import java.util.ArrayList; import java.util.GregorianCalendar; +import java.util.List; +import java.util.concurrent.TimeUnit; import android.content.ContentResolver; import android.content.ContentValues; @@ -11,6 +14,7 @@ import android.net.Uri; import android.support.annotation.Nullable; import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; public class LastUpdateInteractor { @@ -32,7 +36,7 @@ public class LastUpdateInteractor { Cursor cursor = contentResolver.query( UpdatedKeys.CONTENT_URI, new String[] { UpdatedKeys.SEEN_ON_KEYSERVERS }, - UpdatedKeys.MASTER_KEY_ID + " = ?", + Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { "" + masterKeyId }, null ); @@ -75,4 +79,27 @@ public class LastUpdateInteractor { databaseNotifyManager.notifyKeyserverStatusChange(masterKeyId); return insert; } + + public List getFingerprintsForKeysOlderThan(long olderThan, TimeUnit timeUnit) { + Cursor outdatedKeysCursor = contentResolver.query( + KeychainContract.UpdatedKeys.CONTENT_URI, + new String[] { KeychainContract.UpdatedKeys.FINGERPRINT, }, + KeychainContract.UpdatedKeys.LAST_UPDATED + " < ?", + new String[] { Long.toString(timeUnit.toSeconds(olderThan)) }, + null + ); + + List fingerprintList = new ArrayList<>(); + if (outdatedKeysCursor == null) { + return fingerprintList; + } + + while (outdatedKeysCursor.moveToNext()) { + byte[] fingerprint = outdatedKeysCursor.getBlob(0); + fingerprintList.add(fingerprint); + } + outdatedKeysCursor.close(); + + return fingerprintList; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java deleted file mode 100644 index 6799b8d0e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java +++ /dev/null @@ -1,606 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.service; - - -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import android.accounts.Account; -import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.AbstractThreadedSyncAdapter; -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SyncResult; -import android.database.Cursor; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.PowerManager; -import android.os.SystemClock; -import android.support.v4.app.NotificationCompat; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.KeychainApplication; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.network.NetworkReceiver; -import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; -import org.sufficientlysecure.keychain.operations.ImportOperation; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; -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.ParcelableProxy; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.util.ResourceUtils; -import timber.log.Timber; - - -public class KeyserverSyncAdapterService extends Service { - - // how often a sync should be initiated, in s - public static final long SYNC_INTERVAL = - Constants.DEBUG_KEYSERVER_SYNC - ? TimeUnit.MINUTES.toSeconds(1) : TimeUnit.DAYS.toSeconds(3); - // time since last update after which a key should be updated again, in s - public static final long KEY_UPDATE_LIMIT = - Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7); - // time by which a sync is postponed in case screen is on - public static final long SYNC_POSTPONE_TIME = - Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5); - // Time taken by Orbot before a new circuit is created - public static final int ORBOT_CIRCUIT_TIMEOUT_SECONDS = - Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10); - - - private static final String ACTION_IGNORE_TOR = "ignore_tor"; - private static final String ACTION_UPDATE_ALL = "update_all"; - 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"; - - private AtomicBoolean mCancelled = new AtomicBoolean(false); - - @Override - public int onStartCommand(final Intent intent, int flags, final int startId) { - if (intent == null || intent.getAction() == null) { - // introduced due to https://github.com/open-keychain/open-keychain/issues/1573 - return START_NOT_STICKY; // we can't act on this Intent and don't want it redelivered - } - - if (!isSyncEnabled()) { - // if we have initiated a sync, but the user disabled it in preferences since - return START_NOT_STICKY; - } - - switch (intent.getAction()) { - case ACTION_CANCEL: { - mCancelled.set(true); - return START_NOT_STICKY; - } - // the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting - // the sync directly from the notification is possible while the screen is on with - // UPDATE_ALL, but a postponed sync is only started if screen is off - case ACTION_SYNC_NOW: { - // this checks for screen on/off before sync, and postpones the sync if on - ContentResolver.requestSync( - new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), - Constants.PROVIDER_AUTHORITY, - new Bundle() - ); - return START_NOT_STICKY; - } - case ACTION_UPDATE_ALL: { - // does not check for screen on/off - asyncKeyUpdate(this, CryptoInputParcel.createCryptoInputParcel(), startId); - // we depend on handleUpdateResult to call stopSelf when it is no longer necessary - // for the intent to be redelivered - return START_REDELIVER_INTENT; - } - case ACTION_IGNORE_TOR: { - NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); - asyncKeyUpdate(this, CryptoInputParcel.createCryptoInputParcel(ParcelableProxy.getForNoProxy()), - startId); - // we depend on handleUpdateResult to call stopSelf when it is no longer necessary - // for the intent to be redelivered - return START_REDELIVER_INTENT; - } - case ACTION_START_ORBOT: { - NotificationManager manager = (NotificationManager) - getSystemService(NOTIFICATION_SERVICE); - manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); - - Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class); - startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true); - - Messenger messenger = new Messenger( - new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: { - startServiceWithUpdateAll(); - break; - } - case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: - case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: { - // not possible since we proceed to Orbot's Activity - // directly, by starting OrbotRequiredDialogActivity with - // EXTRA_START_ORBOT set to true - break; - } - } - } - } - ); - startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger); - startActivity(startOrbot); - // since we return START_NOT_STICKY, we also postpone the sync as a backup in case - // the service is killed before OrbotRequiredDialogActivity can get back to us - postponeSync(); - // if use START_REDELIVER_INTENT, we might annoy the user by repeatedly starting the - // Orbot Activity when our service is killed and restarted - return START_NOT_STICKY; - } - case ACTION_DISMISS_NOTIFICATION: { - NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); - return START_NOT_STICKY; - } - } - return START_NOT_STICKY; - } - - private class KeyserverSyncAdapter extends AbstractThreadedSyncAdapter { - - public KeyserverSyncAdapter() { - super(KeyserverSyncAdapterService.this, true); - } - - @Override - public void onPerformSync(Account account, Bundle extras, String authority, - ContentProviderClient provider, SyncResult syncResult) { - - 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; - } - } - Timber.d("Performing a keyserver sync!"); - PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this - .getSystemService(Context.POWER_SERVICE); - @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20 - boolean isScreenOn = pm.isScreenOn(); - - if (!isScreenOn) { - startServiceWithUpdateAll(); - } else { - postponeSync(); - } - } - - @Override - public void onSyncCanceled() { - super.onSyncCanceled(); - cancelUpdates(KeyserverSyncAdapterService.this); - } - } - - @Override - public IBinder onBind(Intent intent) { - return new KeyserverSyncAdapter().getSyncAdapterBinder(); - } - - /** - * 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 - * - * @param result result of keyserver sync - * @param startId startId provided to the onStartCommand call which resulted in this sync - */ - private void handleUpdateResult(ImportKeyResult result, final int startId) { - if (result.isPending()) { - Timber.d("Orbot required for sync but not running, attempting to start"); - // result is pending due to Orbot not being started - // try to start it silently, if disabled show notifications - new OrbotHelper.SilentStartManager() { - @Override - protected void onOrbotStarted() { - // retry the update - startServiceWithUpdateAll(); - stopSelf(startId); // startServiceWithUpdateAll will deliver a new Intent - } - - @Override - protected void onSilentStartDisabled() { - // show notification - NotificationManager manager = - (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, - getOrbotNoification(KeyserverSyncAdapterService.this)); - // further action on user interaction with notification, intent should not be - // redelivered, therefore: - stopSelf(startId); - } - }.startOrbotAndListen(this, false); - // if we're killed before we get a response from Orbot, we need the intent to be - // redelivered, so no stopSelf(int) here - } else if (isUpdateCancelled()) { - Timber.d("Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME - + "ms"); - postponeSync(); - // postponeSync creates a new intent, so we don't need this to be redelivered - stopSelf(startId); - } else { - Timber.d("Keyserver sync completed: Updated: " + result.mUpdatedKeys - + " Failed: " + result.mBadKeys); - // key sync completed successfully, we can stop - stopSelf(startId); - } - } - - private void postponeSync() { - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class); - serviceIntent.setAction(ACTION_SYNC_NOW); - PendingIntent pi = PendingIntent.getService(this, 0, serviceIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - alarmManager.set( - AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + SYNC_POSTPONE_TIME, - pi - ); - } - - private void asyncKeyUpdate(final Context context, - final CryptoInputParcel cryptoInputParcel, final int startId) { - new Thread(new Runnable() { - @Override - public void run() { - ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel); - handleUpdateResult(result, startId); - } - }).start(); - } - - private synchronized ImportKeyResult updateKeysFromKeyserver(final Context context, - final CryptoInputParcel cryptoInputParcel) { - mCancelled.set(false); - - ArrayList keyList = getKeysToUpdate(context); - - if (isUpdateCancelled()) { // if we've already been cancelled - return new ImportKeyResult(OperationResult.RESULT_CANCELLED, - new OperationResult.OperationLog()); - } - - if (cryptoInputParcel.getParcelableProxy() == null) { - // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync - if (Preferences.getPreferences(context).getParcelableProxy().isTorEnabled()) { - return staggeredUpdate(context, keyList, cryptoInputParcel); - } else { - return directUpdate(context, keyList, cryptoInputParcel); - } - } else { - return directUpdate(context, keyList, cryptoInputParcel); - } - } - - private ImportKeyResult directUpdate(Context context, ArrayList keyList, - CryptoInputParcel cryptoInputParcel) { - Timber.d("Starting normal update"); - ImportOperation importOp = new ImportOperation(context, - KeyWritableRepository.create(context), null); - return importOp.execute( - ImportKeyringParcel.createImportKeyringParcel(keyList, - Preferences.getPreferences(context).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(Context context, ArrayList 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 " + waitTime + "s"); - 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 (isUpdateCancelled()) { - return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED, - new OperationResult.OperationLog()); - } - ImportKeyResult result = - new ImportOperation(context, KeyWritableRepository.create(context), null, mCancelled) - .execute( - ImportKeyringParcel.createImportKeyringParcel( - keyWrapper, - Preferences.getPreferences(context) - .getPreferredKeyserver() - ), - cryptoInputParcel - ); - if (result.isPending()) { - return result; - } - accumulator.accumulateKeyImport(result); - } - return accumulator.getConsolidatedResult(); - } - - /** - * 1. Get keys which have been updated recently and therefore do not need to - * be updated now - * 2. Get list of all keys and filter out ones that don't need to be updated - * 3. Return keys to be updated - * - * @return list of keys that require update - */ - private ArrayList getKeysToUpdate(Context context) { - - // 1. Get keys which have been updated recently and don't need to updated now - final int INDEX_UPDATED_KEYS_MASTER_KEY_ID = 0; - final int INDEX_LAST_UPDATED = 1; - - // all time in seconds not milliseconds - final long CURRENT_TIME = GregorianCalendar.getInstance().getTimeInMillis() / 1000; - Cursor updatedKeysCursor = context.getContentResolver().query( - KeychainContract.UpdatedKeys.CONTENT_URI, - new String[]{ - KeychainContract.UpdatedKeys.MASTER_KEY_ID, - KeychainContract.UpdatedKeys.LAST_UPDATED - }, - "? - " + KeychainContract.UpdatedKeys.LAST_UPDATED + " < " + KEY_UPDATE_LIMIT, - new String[]{"" + CURRENT_TIME}, - null - ); - - ArrayList ignoreMasterKeyIds = new ArrayList<>(); - while (updatedKeysCursor != null && updatedKeysCursor.moveToNext()) { - long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID); - Timber.d("Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {" - + updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s"); - ignoreMasterKeyIds.add(masterKeyId); - } - if (updatedKeysCursor != null) { - updatedKeysCursor.close(); - } - - // 2. Make a list of public keys which should be updated - final int INDEX_MASTER_KEY_ID = 0; - final int INDEX_FINGERPRINT = 1; - Cursor keyCursor = context.getContentResolver().query( - KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), - new String[]{ - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.FINGERPRINT - }, - null, - null, - null - ); - - if (keyCursor == null) { - return new ArrayList<>(); - } - - ArrayList keyList = new ArrayList<>(); - while (keyCursor.moveToNext()) { - long keyId = keyCursor.getLong(INDEX_MASTER_KEY_ID); - if (ignoreMasterKeyIds.contains(keyId)) { - continue; - } - Timber.d("Keyserver sync: Updating {" + keyId + "}"); - byte[] fingerprint = keyCursor.getBlob(INDEX_FINGERPRINT); - String hexKeyId = KeyFormattingUtils.convertKeyIdToHex(keyId); - // we aren't updating from keybase as of now - keyList.add(ParcelableKeyRing.createFromReference(fingerprint, hexKeyId, null, null)); - } - keyCursor.close(); - - return keyList; - } - - private boolean isUpdateCancelled() { - return mCancelled.get(); - } - - /** - * will cancel an update already in progress. We send an Intent to cancel it instead of simply - * modifying a static variable since the service is running in a process that is different from - * the default application process where the UI code runs. - * - * @param context used to send an Intent to the service requesting cancellation. - */ - public static void cancelUpdates(Context context) { - Intent intent = new Intent(context, KeyserverSyncAdapterService.class); - intent.setAction(ACTION_CANCEL); - context.startService(intent); - } - - private Notification getOrbotNoification(Context context) { - NotificationCompat.Builder builder = new NotificationCompat.Builder(context); - builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) - .setLargeIcon(ResourceUtils.getDrawableAsNotificationBitmap(context, R.mipmap.ic_launcher)) - .setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title)) - .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg)) - .setAutoCancel(true); - - // In case the user decides to not use tor - Intent ignoreTorIntent = new Intent(context, KeyserverSyncAdapterService.class); - ignoreTorIntent.setAction(ACTION_IGNORE_TOR); - PendingIntent ignoreTorPi = PendingIntent.getService( - context, - 0, // security not issue since we're giving this pending intent to Notification Manager - ignoreTorIntent, - PendingIntent.FLAG_CANCEL_CURRENT - ); - - builder.addAction(R.drawable.ic_stat_tor_off, - context.getString(R.string.keyserver_sync_orbot_notif_ignore), - ignoreTorPi); - - Intent startOrbotIntent = new Intent(context, KeyserverSyncAdapterService.class); - startOrbotIntent.setAction(ACTION_START_ORBOT); - PendingIntent startOrbotPi = PendingIntent.getService( - context, - 0, // security not issue since we're giving this pending intent to Notification Manager - startOrbotIntent, - PendingIntent.FLAG_CANCEL_CURRENT - ); - - builder.addAction(R.drawable.ic_stat_tor, - context.getString(R.string.keyserver_sync_orbot_notif_start), - startOrbotPi - ); - builder.setContentIntent(startOrbotPi); - - return builder.build(); - } - - public static void enableKeyserverSync(Context context) { - Account account = KeychainApplication.createAccountIfNecessary(context); - - if (account == null) { - // account failed to be created for some reason, nothing we can do here - return; - } - - ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true); - - updateInterval(context); - } - - /** - * creates a new sync if one does not exist, or updates an existing sync if the sync interval - * has changed. - */ - public static void updateInterval(Context context) { - Account account = KeychainApplication.createAccountIfNecessary(context); - - if (account == null) { - // account failed to be created for some reason, nothing we can do here - return; - } - - 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() { - Account account = KeychainApplication.createAccountIfNecessary(this); - - // if account is null, it could not be created for some reason, so sync cannot exist - return account != null - && ContentResolver.getSyncAutomatically(account, Constants.PROVIDER_AUTHORITY); - } - - private void startServiceWithUpdateAll() { - Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class); - serviceIntent.setAction(ACTION_UPDATE_ALL); - this.startService(serviceIntent); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java index c1565fc9d..b6df0f1db 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java @@ -17,7 +17,11 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.ProgressDialog; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; @@ -25,14 +29,17 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.FragmentActivity; +import android.support.v4.app.NotificationCompat; import android.view.ContextThemeWrapper; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; +import org.sufficientlysecure.keychain.util.ResourceUtils; import timber.log.Timber; @@ -169,4 +176,36 @@ public class OrbotRequiredDialogActivity extends FragmentActivity } } } + + public static void showOrbotRequiredNotification(Context context) { + NotificationManager manager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); + if (manager != null) { + manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, createOrbotNotification(context)); + } + } + + private static Notification createOrbotNotification(Context context) { + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) + .setLargeIcon(ResourceUtils.getDrawableAsNotificationBitmap(context, R.mipmap.ic_launcher)) + .setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title)) + .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg)) + .setAutoCancel(true); + + Intent startOrbotIntent = new Intent(context, OrbotRequiredDialogActivity.class); + startOrbotIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startOrbotIntent.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true); + PendingIntent startOrbotPi = PendingIntent.getActivity( + context, 0, startOrbotIntent, PendingIntent.FLAG_CANCEL_CURRENT + ); + + builder.addAction(R.drawable.ic_stat_tor, + context.getString(R.string.keyserver_sync_orbot_notif_start), + startOrbotPi + ); + builder.setContentIntent(startOrbotPi); + + return builder.build(); + } + } \ No newline at end of file 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 4bcbba382..cc064aa26 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -18,6 +18,11 @@ package org.sufficientlysecure.keychain.ui; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.List; + import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; @@ -48,20 +53,16 @@ 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.keyimport.HkpKeyserverAddress; +import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import timber.log.Timber; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.util.ArrayList; -import java.util.List; - public class SettingsActivity extends AppCompatPreferenceActivity { public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; @@ -398,6 +399,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { * This fragment shows the keyserver/wifi-only-sync/contacts sync preferences */ public static class SyncPrefsFragment extends PresetPreferenceFragment { + boolean syncPrefChanged = false; @Override public void onCreate(Bundle savedInstanceState) { @@ -405,6 +407,22 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // Load the preferences from an XML resource addPreferencesFromResource(R.xml.sync_preferences); + + findPreference(Constants.Pref.SYNC_KEYSERVER).setOnPreferenceChangeListener( + (preference, newValue) -> { + syncPrefChanged = true; + return true; + }); + } + + @Override + public void onPause() { + super.onPause(); + + if (syncPrefChanged) { + KeyserverSyncManager.updateKeyserverSyncSchedule(getActivity(), true); + syncPrefChanged = false; + } } @Override @@ -413,12 +431,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { // 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 final Account account = KeychainApplication.createAccountIfNecessary(getActivity()); - // for keyserver sync - initializeSyncCheckBox( - (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), - account, - Constants.PROVIDER_AUTHORITY - ); // for contacts sync initializeSyncCheckBox( (SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS), 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 bb1949537..626bd8fb9 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 @@ -33,7 +33,6 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; -import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; /** @@ -77,7 +76,6 @@ public abstract class BaseActivity extends AppCompatActivity { } public static void onResumeChecks(Context context) { - KeyserverSyncAdapterService.cancelUpdates(context); // in case user has disabled sync from Android account settings ContactSyncAdapterService.deleteIfSyncDisabled(context); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index bb5f7b105..c9ef239d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -363,6 +363,11 @@ public class KeyFormattingUtils { return idHex; } + public static String beautifyKeyId(byte[] fingerprint) { + long keyId = KeyFormattingUtils.convertFingerprintToKeyId(fingerprint); + return beautifyKeyId(keyId); + } + /** * Makes a human-readable version of a key ID, which is usually 64 bits: lower-case, no * leading 0x, space-separated quartets (for keys whose length in hex is divisible by 4) 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 a44c101fb..38773c116 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -18,27 +18,24 @@ package org.sufficientlysecure.keychain.util; -import android.accounts.Account; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.ListIterator; + import android.annotation.SuppressLint; -import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Parcelable; import android.preference.PreferenceManager; import android.support.annotation.Nullable; + import com.google.auto.value.AutoValue; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.Pref; -import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; -import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService; import timber.log.Timber; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.ListIterator; - /** * Singleton Implementation of a Preference Helper @@ -324,23 +321,6 @@ 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(); - } - // cloud prefs public CloudSearchPrefs getCloudSearchPrefs() { @@ -361,6 +341,18 @@ public class Preferences { editor.commit(); } + public boolean isKeyserverSyncEnabled() { + return mSharedPreferences.getBoolean(Pref.SYNC_KEYSERVER, true); + } + + public boolean isKeyserverSyncScheduled() { + return mSharedPreferences.getBoolean(Pref.SYNC_IS_SCHEDULED, false); + } + + public void setKeyserverSyncScheduled(boolean isScheduled) { + mSharedPreferences.edit().putBoolean(Pref.SYNC_IS_SCHEDULED, isScheduled).apply(); + } + @AutoValue public static abstract class CloudSearchPrefs implements Parcelable { public abstract boolean isKeyserverEnabled(); @@ -431,7 +423,7 @@ public class Preferences { editor.commit(); } - public void upgradePreferences(Context context) { + public void upgradePreferences() { int oldVersion = mSharedPreferences.getInt(Constants.Pref.PREF_VERSION, 0); boolean requiresUpgrade = oldVersion < Constants.Defaults.PREF_CURRENT_VERSION; @@ -447,9 +439,7 @@ public class Preferences { case 4: { setTheme(Constants.Pref.Theme.DEFAULT); } - case 5: { - KeyserverSyncAdapterService.enableKeyserverSync(context); - } + case 5: case 6: case 7: { addOnionToSks(); diff --git a/OpenKeychain/src/main/res/xml/sync_preferences.xml b/OpenKeychain/src/main/res/xml/sync_preferences.xml index 600ccc9e8..6e57376e1 100644 --- a/OpenKeychain/src/main/res/xml/sync_preferences.xml +++ b/OpenKeychain/src/main/res/xml/sync_preferences.xml @@ -1,7 +1,8 @@ Date: Wed, 13 Jun 2018 18:20:41 +0200 Subject: [PATCH 005/124] Show notification during key sync --- .../keychain/Constants.java | 7 +- .../keychain/keysync/KeyserverSyncWorker.java | 89 +++++++++++++++++-- .../service/PassphraseCacheService.java | 3 +- .../ui/OrbotRequiredDialogActivity.java | 4 +- OpenKeychain/src/main/res/values/strings.xml | 4 + 5 files changed, 97 insertions(+), 10 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 2da51d506..fce89989b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -108,9 +108,14 @@ public final class Constants { public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain"); } - public static final class Notification { + public static final class NotificationIds { public static final int PASSPHRASE_CACHE = 1; public static final int KEYSERVER_SYNC_FAIL_ORBOT = 2; + public static final int KEYSERVER_SYNC = 3; + } + + public static final class NotificationChannels { + public static final String KEYSERVER_SYNC = "keyserverSync"; } public static final class Pref { 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 240389d83..94b996644 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java @@ -7,16 +7,25 @@ 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; +import android.os.Build; import android.support.annotation.NonNull; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationCompat.Builder; 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.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.LastUpdateInteractor; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; @@ -24,6 +33,7 @@ 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; @@ -65,10 +75,19 @@ public class KeyserverSyncWorker extends Worker { // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync CryptoInputParcel cryptoInputParcel = CryptoInputParcel.createCryptoInputParcel(); - if (preferences.getParcelableProxy().isTorEnabled()) { - return staggeredUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel); - } else { - return directUpdate(context, staleKeyParcelableKeyRings, cryptoInputParcel); + 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(); } } @@ -82,9 +101,9 @@ public class KeyserverSyncWorker extends Worker { } private ImportKeyResult directUpdate(Context context, List keyList, - CryptoInputParcel cryptoInputParcel) { + CryptoInputParcel cryptoInputParcel, Progressable notificationProgressable) { Timber.d("Starting normal update"); - ImportOperation importOp = new ImportOperation(context, keyWritableRepository, null); + ImportOperation importOp = new ImportOperation(context, keyWritableRepository, notificationProgressable); return importOp.execute( ImportKeyringParcel.createImportKeyringParcel(keyList, preferences.getPreferredKeyserver()), cryptoInputParcel @@ -190,6 +209,64 @@ public class KeyserverSyncWorker extends Worker { return accumulator.getConsolidatedResult(); } + private Progressable notificationShowForProgress() { + final Context context = getApplicationContext(); + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager == null) { + 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); + } + + 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(NotificationCompat.PRIORITY_LOW) + .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()); + } + + @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()); + } + + @Override + public void setProgress(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()); + } + + @Override + public void setPreventCancel() { + } + }; + } + + private void notificationRemove() { + NotificationManager notificationManager = + (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + notificationManager.cancel(NotificationIds.KEYSERVER_SYNC); + } + } + @Override public void onStopped() { super.onStopped(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 8945a3652..fe9fc4987 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -38,6 +38,7 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Constants.NotificationIds; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; @@ -488,7 +489,7 @@ public class PassphraseCacheService extends Service { private void updateService() { if (mPassphraseCache.size() > 0) { - startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification()); + startForeground(NotificationIds.PASSPHRASE_CACHE, getNotification()); } else { // stop whole service if no cached passphrases remaining Timber.d("PassphraseCacheService: No passphrases remaining in memory, stopping service!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java index b6df0f1db..f96107619 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java @@ -32,7 +32,7 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.NotificationCompat; import android.view.ContextThemeWrapper; -import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Constants.NotificationIds; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -180,7 +180,7 @@ public class OrbotRequiredDialogActivity extends FragmentActivity public static void showOrbotRequiredNotification(Context context) { NotificationManager manager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); if (manager != null) { - manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, createOrbotNotification(context)); + manager.notify(NotificationIds.KEYSERVER_SYNC_FAIL_ORBOT, createOrbotNotification(context)); } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index c36650f1b..97d5aabf1 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -2024,4 +2024,8 @@ Found key data in clipboard! View + + Keyserver update + Updating keys… + Key %d / %d From 8bf71b893c8a797708bc2612427dfc5f8a6fef66 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 14 Jun 2018 18:16:40 +0200 Subject: [PATCH 006/124] use KeyserverSyncWorker for updating keys in the foreground, too --- .../keychain/Constants.java | 1 + .../keychain/KeychainApplication.java | 3 - .../keysync/KeyserverSyncManager.java | 47 ++++++++-- .../keychain/keysync/KeyserverSyncWorker.java | 92 +++++++++++-------- .../keychain/ui/KeyListFragment.java | 90 ++---------------- OpenKeychain/src/main/res/menu/key_list.xml | 6 ++ OpenKeychain/src/main/res/values/strings.xml | 5 + 7 files changed, 115 insertions(+), 129 deletions(-) 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 From f22c761376fa581be742b579ddc6111da1448bf6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 15 Jun 2018 13:02:33 +0200 Subject: [PATCH 007/124] fix unit tests, add shadow for WorkerManager --- ...stHelpers.java => AndroidTestHelpers.java} | 3 +- .../keychain/remote/OpenPgpServiceTest.java | 6 +-- .../ui/AsymmetricFileOperationTests.java | 14 +++--- .../ui/AsymmetricTextOperationTests.java | 6 +-- .../keychain/ui/EditKeyTest.java | 16 ++----- .../keychain/ui/MiscCryptOperationTests.java | 21 ++++----- .../ui/SymmetricTextOperationTests.java | 4 +- .../keychain/ui/ViewKeyAdvShareTest.java | 4 +- .../widget/EncryptKeyCompletionViewTest.java | 2 +- .../keychain/KeychainTestRunner.java | 3 ++ .../keychain/TestHelpers.java | 43 +++++++++++++++++++ .../keychain/pgp/InputDataOperationTest.java | 43 +++++++++++-------- .../keychain/shadows/ShadowWorkManager.java | 19 ++++++++ 13 files changed, 117 insertions(+), 67 deletions(-) rename OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/{TestHelpers.java => AndroidTestHelpers.java} (99%) create mode 100644 OpenKeychain/src/test/java/org/sufficientlysecure/keychain/TestHelpers.java create mode 100644 OpenKeychain/src/test/java/org/sufficientlysecure/keychain/shadows/ShadowWorkManager.java diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java similarity index 99% rename from OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java rename to OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java index 5a6509080..f5353540d 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/TestHelpers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java @@ -53,7 +53,7 @@ import static org.hamcrest.CoreMatchers.endsWith; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor; -public class TestHelpers { +public class AndroidTestHelpers { public static void dismissSnackbar() { onView(withClassName(endsWith("Snackbar"))) @@ -161,5 +161,4 @@ public class TestHelpers { PassphraseCacheService.clearCachedPassphrases(context); } - } diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java index 1e84ea879..ccd83a777 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/remote/OpenPgpServiceTest.java @@ -9,7 +9,6 @@ import android.support.test.InstrumentationRegistry; import android.support.test.rule.ServiceTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; -import android.widget.AdapterView; import org.junit.Before; import org.junit.Rule; @@ -23,17 +22,14 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import static android.support.test.espresso.Espresso.closeSoftKeyboard; -import static android.support.test.espresso.Espresso.onData; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.typeText; -import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; -import static org.sufficientlysecure.keychain.TestHelpers.cleanupForTests; -import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.cleanupForTests; @RunWith(AndroidJUnit4.class) diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricFileOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricFileOperationTests.java index 0cd3681dd..0930ef7c4 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricFileOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricFileOperationTests.java @@ -32,7 +32,7 @@ import android.widget.AdapterView; import org.junit.Before; import org.junit.Rule; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.TestHelpers; +import org.sufficientlysecure.keychain.AndroidTestHelpers; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -61,11 +61,11 @@ import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; -import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.getImageNames; -import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; -import static org.sufficientlysecure.keychain.TestHelpers.pickRandom; -import static org.sufficientlysecure.keychain.TestHelpers.randomString; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.getImageNames; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.pickRandom; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString; import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild; @@ -95,7 +95,7 @@ public class AsymmetricFileOperationTests { public void setUp() throws Exception { Activity activity = mActivity.getActivity(); - TestHelpers.copyFiles(); + AndroidTestHelpers.copyFiles(); // import these two, make sure they're there importKeysFromResource(activity, "x.sec.asc"); diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricTextOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricTextOperationTests.java index e2ea0c52f..b34ea6c81 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricTextOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/AsymmetricTextOperationTests.java @@ -43,9 +43,9 @@ import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; -import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; -import static org.sufficientlysecure.keychain.TestHelpers.randomString; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString; import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java index 139967264..10939b8e9 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java @@ -20,37 +20,27 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; -import android.support.test.espresso.ViewAction; -import android.support.test.espresso.action.ViewActions; -import android.support.test.espresso.contrib.RecyclerViewActions; -import android.support.test.espresso.matcher.ViewMatchers; import android.support.test.rule.ActivityTestRule; import android.support.v7.widget.RecyclerView; -import android.widget.AdapterView; -import org.hamcrest.Description; -import org.hamcrest.Matcher; import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.runners.MethodSorters; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.matcher.CustomMatchers; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import static android.support.test.espresso.Espresso.onData; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; -import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; -import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyHolderId; -import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; + //TODO This test is disabled because it needs to be fixed to work with updated code @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/MiscCryptOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/MiscCryptOperationTests.java index 784f26fe0..74b79f020 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/MiscCryptOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/MiscCryptOperationTests.java @@ -27,18 +27,15 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build.VERSION_CODES; -import android.support.test.espresso.contrib.RecyclerViewActions; import android.support.test.espresso.intent.Intents; import android.support.test.espresso.intent.rule.IntentsTestRule; import android.support.v7.widget.RecyclerView; -import android.widget.AdapterView; import org.junit.Before; import org.junit.Rule; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.TestHelpers; -import org.sufficientlysecure.keychain.matcher.CustomMatchers; +import org.sufficientlysecure.keychain.AndroidTestHelpers; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Preferences; @@ -46,7 +43,6 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.File; import static android.support.test.InstrumentationRegistry.getInstrumentation; -import static android.support.test.espresso.Espresso.onData; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu; import static android.support.test.espresso.Espresso.pressBack; @@ -67,16 +63,15 @@ import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.hasItem; -import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.dismissSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.getImageNames; -import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; -import static org.sufficientlysecure.keychain.TestHelpers.pickRandom; -import static org.sufficientlysecure.keychain.TestHelpers.randomString; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.dismissSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.getImageNames; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.pickRandom; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyHolderId; -import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable; //TODO This test is disabled because it needs to be fixed to work with updated code @@ -104,7 +99,7 @@ public class MiscCryptOperationTests { mActivity = mActivityRule.getActivity(); - TestHelpers.copyFiles(); + AndroidTestHelpers.copyFiles(); // import these two, make sure they're there importKeysFromResource(mActivity, "x.sec.asc"); diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java index d3e4a8a86..b51be1e09 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java @@ -52,8 +52,8 @@ import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.Matchers.equalTo; -import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.randomString; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withEncryptionStatus; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureNone; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java index ebf5619ad..83463d179 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java @@ -47,8 +47,8 @@ import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.sufficientlysecure.keychain.TestHelpers.checkAndDismissSnackbar; -import static org.sufficientlysecure.keychain.TestHelpers.cleanupForTests; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkAndDismissSnackbar; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.cleanupForTests; //TODO This test is disabled because it needs to be fixed to work with updated code @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionViewTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionViewTest.java index e82914652..554df0a51 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionViewTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionViewTest.java @@ -40,7 +40,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.CoreMatchers.allOf; -import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource; +import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource; import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId; import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyToken; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java index 37439d8b8..31a1ebd3a 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java @@ -3,6 +3,8 @@ package org.sufficientlysecure.keychain; import org.junit.runners.model.InitializationError; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.sufficientlysecure.keychain.shadows.ShadowWorkManager; + public class KeychainTestRunner extends RobolectricTestRunner { @@ -15,6 +17,7 @@ public class KeychainTestRunner extends RobolectricTestRunner { return new Config.Builder() .setSdk(27) .setConstants(WorkaroundBuildConfig.class) + .setShadows(new Class[] { ShadowWorkManager.class }) .build(); } } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/TestHelpers.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/TestHelpers.java new file mode 100644 index 000000000..b6305bb87 --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/TestHelpers.java @@ -0,0 +1,43 @@ +package org.sufficientlysecure.keychain; + + +import android.content.ContentValues; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.mockito.Matchers; + + +public class TestHelpers { + public static ContentValues cvContains(ContentValues value) { + return Matchers.argThat(new BaseMatcher() { + @Override + public boolean matches(Object item) { + if (item instanceof ContentValues) { + ContentValues cv = (ContentValues) item; + for (String key : value.keySet()) { + if (!cv.containsKey(key)) { + return false; + } + + Object ours = value.get(key); + Object theirs = cv.get(key); + if (ours == null && theirs == null) { + continue; + } + if (ours == null || !ours.equals(theirs)) { + return false; + } + } + return true; + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendValue(value); + } + }); + } +} diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java index a40c31662..6597fcdb3 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java @@ -34,6 +34,7 @@ import org.openintents.openpgp.OpenPgpMetadata; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.TestHelpers; import org.sufficientlysecure.keychain.operations.InputDataOperation; import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -49,12 +50,16 @@ import java.io.PrintStream; import java.security.Security; import java.util.ArrayList; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sufficientlysecure.keychain.TestHelpers.cvContains; + @RunWith(KeychainTestRunner.class) public class InputDataOperationTest { @@ -137,33 +142,33 @@ public class InputDataOperationTest { Assert.assertNull(result.mDecryptVerifyResult); ArrayList outUris = result.getOutputUris(); - Assert.assertEquals("must have two output URIs", 2, outUris.size()); - Assert.assertEquals("first uri must be the one we provided", fakeOutputUri1, outUris.get(0)); + assertEquals("must have two output URIs", 2, outUris.size()); + assertEquals("first uri must be the one we provided", fakeOutputUri1, outUris.get(0)); verify(mockResolver).openOutputStream(result.getOutputUris().get(0), "w"); - Assert.assertEquals("second uri must be the one we provided", fakeOutputUri2, outUris.get(1)); + assertEquals("second uri must be the one we provided", fakeOutputUri2, outUris.get(1)); verify(mockResolver).openOutputStream(result.getOutputUris().get(1), "w"); ContentValues contentValues = new ContentValues(); contentValues.put("name", "data.txt"); contentValues.put("mimetype", "text/plain"); - verify(mockResolver).insert(TemporaryFileProvider.CONTENT_URI, contentValues); + verify(mockResolver).insert(eq(TemporaryFileProvider.CONTENT_URI), cvContains(contentValues)); contentValues.put("name", (String) null); contentValues.put("mimetype", "text/testvalue"); - verify(mockResolver).insert(TemporaryFileProvider.CONTENT_URI, contentValues); + verify(mockResolver).insert(eq(TemporaryFileProvider.CONTENT_URI), cvContains(contentValues)); // quoted-printable returns windows style line endings for some reason? - Assert.assertEquals("first part must have expected content", + assertEquals("first part must have expected content", "message part 1\r\n", new String(outStream1.toByteArray())); - Assert.assertEquals("second part must have expected content", + assertEquals("second part must have expected content", "message part 2.1\nmessage part 2.2\n", new String(outStream2.toByteArray())); OpenPgpMetadata metadata = result.mMetadata.get(0); - Assert.assertEquals("text/plain", metadata.getMimeType()); - Assert.assertEquals("utf-8", metadata.getCharset()); + assertEquals("text/plain", metadata.getMimeType()); + assertEquals("utf-8", metadata.getCharset()); metadata = result.mMetadata.get(1); - Assert.assertEquals("text/testvalue", metadata.getMimeType()); - Assert.assertEquals("iso-8859-1", metadata.getCharset()); + assertEquals("text/testvalue", metadata.getMimeType()); + assertEquals("iso-8859-1", metadata.getCharset()); } @Test @@ -184,9 +189,9 @@ public class InputDataOperationTest { Assert.assertNull(result.mDecryptVerifyResult); OpenPgpMetadata metadata = result.mMetadata.get(0); - Assert.assertEquals("text/plain", metadata.getMimeType()); + assertEquals("text/plain", metadata.getMimeType()); - Assert.assertEquals("charset should be set since it was explicitly specified", + assertEquals("charset should be set since it was explicitly specified", "utf-8", metadata.getCharset()); Assert.assertTrue("faulty charset should have been detected", result.getLog().containsType(LogType.MSG_DATA_MIME_CHARSET_FAULTY)); @@ -210,7 +215,7 @@ public class InputDataOperationTest { Assert.assertNull(result.mDecryptVerifyResult); OpenPgpMetadata metadata = result.mMetadata.get(0); - Assert.assertEquals("text/plain", metadata.getMimeType()); + assertEquals("text/plain", metadata.getMimeType()); Assert.assertNull("charset was bad so it should not be set", metadata.getCharset()); Assert.assertTrue("faulty charset should have been detected", @@ -231,9 +236,9 @@ public class InputDataOperationTest { Assert.assertNull(result.mDecryptVerifyResult); OpenPgpMetadata metadata = result.mMetadata.get(0); - Assert.assertEquals("text/plain", metadata.getMimeType()); + assertEquals("text/plain", metadata.getMimeType()); - Assert.assertEquals("charset should be set since it was guessed and not faulty", + assertEquals("charset should be set since it was guessed and not faulty", "utf-8", metadata.getCharset()); Assert.assertTrue("charset should have been guessed", result.getLog().containsType(LogType.MSG_DATA_MIME_CHARSET_GUESS)); @@ -253,9 +258,9 @@ public class InputDataOperationTest { Assert.assertNull(result.mDecryptVerifyResult); OpenPgpMetadata metadata = result.mMetadata.get(0); - Assert.assertEquals("text/plain", metadata.getMimeType()); + assertEquals("text/plain", metadata.getMimeType()); - Assert.assertEquals("charset should be set since it was guessed and not faulty", + assertEquals("charset should be set since it was guessed and not faulty", "utf-8", metadata.getCharset()); Assert.assertTrue("charset should have been guessed", result.getLog().containsType(LogType.MSG_DATA_MIME_CHARSET_GUESS)); @@ -280,7 +285,7 @@ public class InputDataOperationTest { Assert.assertTrue("should not be mime parsed", result.getLog().containsType(LogType.MSG_DATA_MIME_NONE)); - Assert.assertEquals("output uri should simply be passed-through input uri", + assertEquals("output uri should simply be passed-through input uri", result.getOutputUris().get(0), FAKE_CONTENT_INPUT_URI_1); } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/shadows/ShadowWorkManager.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/shadows/ShadowWorkManager.java new file mode 100644 index 000000000..f45ba8f6e --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/shadows/ShadowWorkManager.java @@ -0,0 +1,19 @@ +package org.sufficientlysecure.keychain.shadows; + + +import androidx.work.WorkManager; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import static org.mockito.Mockito.mock; + + +@Implements(WorkManager.class) +public class ShadowWorkManager { + + @Implementation + public static WorkManager getInstance() { + return mock(WorkManager.class); + } + +} From a3fd1609dff4f98d02dbfedb0da08e9d00cbfa77 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 20 Jun 2018 01:09:18 +0200 Subject: [PATCH 008/124] fix table reference --- .../keychain/ui/keyview/loader/KeyserverStatusDao.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java index 16e6828c6..7fdc0c6d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java @@ -25,6 +25,7 @@ import android.content.Context; import android.database.Cursor; import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import timber.log.Timber; @@ -50,7 +51,7 @@ public class KeyserverStatusDao { public KeyserverStatus getKeyserverStatus(long masterKeyId) { Cursor cursor = contentResolver.query(UpdatedKeys.CONTENT_URI, PROJECTION, - UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(masterKeyId) }, null); + Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(masterKeyId) }, null); if (cursor == null) { Timber.e("Error loading key items!"); return null; From 59c9f52e85ecc9cb55fdcda0d2b72fba8e15ef72 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 15 Jun 2018 14:34:38 +0200 Subject: [PATCH 009/124] Store secret keys in private storage instead of database --- .../operations/results/OperationResult.java | 1 - .../keychain/provider/KeyRepository.java | 22 +++-- .../provider/KeyWritableRepository.java | 47 ++++------ .../keychain/provider/KeychainContract.java | 14 --- .../keychain/provider/KeychainDatabase.java | 94 +++++++++++-------- .../keychain/provider/KeychainProvider.java | 51 +--------- .../keychain/ApiAllowedKeys.sq | 0 .../sufficientlysecure/keychain/ApiApps.sq | 8 ++ .../keychain/provider/InteropTest.java | 5 +- 9 files changed, 102 insertions(+), 140 deletions(-) create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiApps.sq 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 f3385d9fc..f6426bd8d 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 @@ -419,7 +419,6 @@ public abstract class OperationResult implements Parcelable { // import secret MSG_IS(LogLevel.START, R.string.msg_is), MSG_IS_BAD_TYPE_PUBLIC (LogLevel.WARN, R.string.msg_is_bad_type_public), - MSG_IS_DB_EXCEPTION (LogLevel.DEBUG, R.string.msg_is_db_exception), MSG_IS_ERROR_IO_EXC(LogLevel.DEBUG, R.string.msg_is_error_io_exc), MSG_IS_MERGE_PUBLIC (LogLevel.DEBUG, R.string.msg_is_merge_public), MSG_IS_MERGE_SECRET (LogLevel.DEBUG, R.string.msg_is_merge_secret), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index fce2c131c..e6dcff07d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -56,24 +56,28 @@ public class KeyRepository { final ContentResolver contentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; + final LocalSecretKeyStorage localSecretKeyStorage; OperationLog mLog; int mIndent; public static KeyRepository create(Context context) { ContentResolver contentResolver = context.getContentResolver(); LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); + LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); - return new KeyRepository(contentResolver, localPublicKeyStorage); + return new KeyRepository(contentResolver, localPublicKeyStorage, localSecretKeyStorage); } - private KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage) { - this(contentResolver, localPublicKeyStorage, new OperationLog(), 0); + private KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage, + LocalSecretKeyStorage localSecretKeyStorage) { + this(contentResolver, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0); } KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage, - OperationLog log, int indent) { + LocalSecretKeyStorage localSecretKeyStorage, OperationLog log, int indent) { this.contentResolver = contentResolver; mLocalPublicKeyStorage = localPublicKeyStorage; + this.localSecretKeyStorage = localSecretKeyStorage; mIndent = indent; mLog = log; } @@ -326,14 +330,12 @@ public class KeyRepository { } public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException { - byte[] data = (byte[]) getGenericDataOrNull(KeychainContract.KeyRingData.buildSecretKeyRingUri(masterKeyId), - KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB); - - if (data == null) { + try { + return localSecretKeyStorage.readSecretKey(masterKeyId); + } catch (IOException e) { + Timber.e(e, "Error reading public key from storage!"); throw new NotFoundException(); } - - return data; } public static class NotFoundException extends Exception { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 373332d26..4602112b1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -85,28 +85,31 @@ public class KeyWritableRepository extends KeyRepository { private final Context context; private final LastUpdateInteractor lastUpdateInteractor; - private DatabaseNotifyManager databaseNotifyManager; + private final DatabaseNotifyManager databaseNotifyManager; public static KeyWritableRepository create(Context context) { LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); - LastUpdateInteractor lastUpdateInteractor = LastUpdateInteractor.create(context); + LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - - return new KeyWritableRepository(context, localPublicKeyStorage, lastUpdateInteractor, - databaseNotifyManager); - } + LastUpdateInteractor lastUpdateInteractor = LastUpdateInteractor.create(context); + return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, + lastUpdateInteractor); + } @VisibleForTesting - KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, - LastUpdateInteractor lastUpdateInteractor, DatabaseNotifyManager databaseNotifyManager) { - this(context, localPublicKeyStorage, lastUpdateInteractor, new OperationLog(), 0, - databaseNotifyManager); + KeyWritableRepository(Context context, + LocalPublicKeyStorage localPublicKeyStorage, + LocalSecretKeyStorage localSecretKeyStorage, + DatabaseNotifyManager databaseNotifyManager, + LastUpdateInteractor lastUpdateInteractor) { + this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, lastUpdateInteractor, + new OperationLog(), 0); } private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, - LastUpdateInteractor lastUpdateInteractor, OperationLog log, int indent, - DatabaseNotifyManager databaseNotifyManager) { - super(context.getContentResolver(), localPublicKeyStorage, log, indent); + LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, + LastUpdateInteractor lastUpdateInteractor, OperationLog log, int indent) { + super(context.getContentResolver(), localPublicKeyStorage, localSecretKeyStorage, log, indent); this.context = context; this.databaseNotifyManager = databaseNotifyManager; @@ -601,21 +604,15 @@ public class KeyWritableRepository extends KeyRepository { operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build()); } - private Uri writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException { + private void writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException { byte[] encodedKey = keyRing.getEncoded(); - - ContentValues values = new ContentValues(); - values.put(KeyRingData.MASTER_KEY_ID, masterKeyId); - values.put(KeyRingData.KEY_RING_DATA, encodedKey); - - // insert new version of this keyRing - Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId); - return contentResolver.insert(uri, values); + localSecretKeyStorage.writeSecretKey(masterKeyId, encodedKey); } public boolean deleteKeyRing(long masterKeyId) { try { mLocalPublicKeyStorage.deletePublicKey(masterKeyId); + localSecretKeyStorage.deleteSecretKey(masterKeyId); } catch (IOException e) { Timber.e(e, "Could not delete file!"); return false; @@ -683,11 +680,7 @@ public class KeyWritableRepository extends KeyRepository { // save secret keyring try { - Uri insertedUri = writeSecretKeyRing(keyRing, masterKeyId); - if (insertedUri == null) { - log(LogType.MSG_IS_DB_EXCEPTION); - return SaveKeyringResult.RESULT_ERROR; - } + writeSecretKeyRing(keyRing, masterKeyId); } catch (IOException e) { Timber.e(e, "Failed to encode key!"); log(LogType.MSG_IS_ERROR_IO_EXC); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 396384dd7..85d88d4fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -136,7 +136,6 @@ public class KeychainContract { public static final String PATH_BY_SIGNER = "signer"; public static final String PATH_PUBLIC = "public"; - public static final String PATH_SECRET = "secret"; public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_LINKED_IDS = "linked_ids"; public static final String PATH_KEYS = "keys"; @@ -239,19 +238,6 @@ public class KeychainContract { public static Uri buildPublicKeyRingUri(Uri uri) { return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build(); } - - public static Uri buildSecretKeyRingUri() { - return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build(); - } - - public static Uri buildSecretKeyRingUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_SECRET).build(); - } - - public static Uri buildSecretKeyRingUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_SECRET).build(); - } - } public static class Keys implements KeysColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 8350b9da1..25d0f0743 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -24,7 +24,7 @@ import java.io.FileOutputStream; import java.io.IOException; import android.content.Context; -import android.database.SQLException; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; @@ -55,12 +55,11 @@ import timber.log.Timber; */ public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 25; + private static final int DATABASE_VERSION = 26; private Context mContext; public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; - String KEY_RINGS_SECRET = "keyrings_secret"; String KEYS = "keys"; String UPDATED_KEYS = "updated_keys"; String KEY_SIGNATURES = "key_signatures"; @@ -78,14 +77,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { + KeyRingsColumns.KEY_RING_DATA + " BLOB" + ")"; - private static final String CREATE_KEYRINGS_SECRET = - "CREATE TABLE IF NOT EXISTS keyrings_secret (" - + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," - + KeyRingsColumns.KEY_RING_DATA + " BLOB, " - + "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") " - + "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" - + ")"; - private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " (" + KeysColumns.MASTER_KEY_ID + " INTEGER, " @@ -222,7 +213,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { Timber.w("Creating database..."); db.execSQL(CREATE_KEYRINGS_PUBLIC); - db.execSQL(CREATE_KEYRINGS_SECRET); db.execSQL(CREATE_KEYS); db.execSQL(CREATE_USER_PACKETS); db.execSQL(CREATE_CERTS); @@ -302,37 +292,37 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("DROP TABLE IF EXISTS certs"); db.execSQL("DROP TABLE IF EXISTS user_ids"); db.execSQL("CREATE TABLE IF NOT EXISTS user_packets(" - + "master_key_id INTEGER, " - + "type INT, " - + "user_id TEXT, " - + "attribute_data BLOB, " + + "master_key_id INTEGER, " + + "type INT, " + + "user_id TEXT, " + + "attribute_data BLOB, " - + "is_primary INTEGER, " - + "is_revoked INTEGER, " - + "rank INTEGER, " + + "is_primary INTEGER, " + + "is_revoked INTEGER, " + + "rank INTEGER, " - + "PRIMARY KEY(master_key_id, rank), " - + "FOREIGN KEY(master_key_id) REFERENCES " + + "PRIMARY KEY(master_key_id, rank), " + + "FOREIGN KEY(master_key_id) REFERENCES " + "keyrings_public(master_key_id) ON DELETE CASCADE" - + ")"); + + ")"); db.execSQL("CREATE TABLE IF NOT EXISTS certs(" - + "master_key_id INTEGER," - + "rank INTEGER, " // rank of certified uid + + "master_key_id INTEGER," + + "rank INTEGER, " // rank of certified uid - + "key_id_certifier INTEGER, " // certifying key - + "type INTEGER, " - + "verified INTEGER, " - + "creation INTEGER, " + + "key_id_certifier INTEGER, " // certifying key + + "type INTEGER, " + + "verified INTEGER, " + + "creation INTEGER, " - + "data BLOB, " + + "data BLOB, " - + "PRIMARY KEY(master_key_id, rank, " + + "PRIMARY KEY(master_key_id, rank, " + "key_id_certifier), " - + "FOREIGN KEY(master_key_id) REFERENCES " + + "FOREIGN KEY(master_key_id) REFERENCES " + "keyrings_public(master_key_id) ON DELETE CASCADE," - + "FOREIGN KEY(master_key_id, rank) REFERENCES " + + "FOREIGN KEY(master_key_id, rank) REFERENCES " + "user_packets(master_key_id, rank) ON DELETE CASCADE" - + ")"); + + ")"); case 9: // do nothing here, just consolidate case 10: @@ -380,11 +370,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { } */ case 20: - db.execSQL( - "CREATE TABLE IF NOT EXISTS overridden_warnings (" - + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " - + "identifier TEXT NOT NULL UNIQUE " - + ")"); + db.execSQL( + "CREATE TABLE IF NOT EXISTS overridden_warnings (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "identifier TEXT NOT NULL UNIQUE " + + ")"); case 21: try { @@ -403,7 +393,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "master_key_id INTEGER NULL, " + "PRIMARY KEY(package_name, identifier), " + "FOREIGN KEY(package_name) REFERENCES api_apps(package_name) ON DELETE CASCADE" - + ")"); + + ")"); case 23: db.execSQL("CREATE TABLE IF NOT EXISTS key_signatures (" @@ -413,7 +403,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE" + ")"); - case 24: + case 24: { try { db.beginTransaction(); db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO tmp"); @@ -456,9 +446,33 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("CREATE INDEX IF NOT EXISTS uids_by_email ON user_packets (email);"); db.execSQL("DROP INDEX keys_by_rank"); db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);"); + } + + case 25: { + try { + migrateSecretKeysFromDbToLocalStorage(db); + } catch (IOException e) { + throw new IllegalStateException("Error migrating secret keys! This is bad!!"); + } + } + } } + private void migrateSecretKeysFromDbToLocalStorage(SQLiteDatabase db) throws IOException { + LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(mContext); + Cursor cursor = db.rawQuery("SELECT master_key_id, key_ring_data FROM keyrings_secret", null); + while (cursor.moveToNext()) { + long masterKeyId = cursor.getLong(0); + byte[] secretKeyBlob = cursor.getBlob(1); + localSecretKeyStorage.writeSecretKey(masterKeyId, secretKeyBlob); + } + cursor.close(); + + // we'll keep this around for now, but make sure to delete when migration looks ok!! + // db.execSQL("DROP TABLE keyrings_secret"); + } + @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Downgrade is ok for the debug version, makes it easier to work with branches diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index f1d2b3326..1d7e39b6a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -60,14 +60,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RINGS_UNIFIED = 101; private static final int KEY_RINGS_PUBLIC = 102; - private static final int KEY_RINGS_SECRET = 103; private static final int KEY_RINGS_USER_IDS = 104; private static final int KEY_RING_UNIFIED = 200; private static final int KEY_RING_KEYS = 201; private static final int KEY_RING_USER_IDS = 202; private static final int KEY_RING_PUBLIC = 203; - private static final int KEY_RING_SECRET = 204; private static final int KEY_RING_CERTS = 205; private static final int KEY_RING_CERTS_SPECIFIC = 206; private static final int KEY_RING_LINKED_IDS = 207; @@ -118,9 +116,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC, KEY_RINGS_PUBLIC); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_SECRET, - KEY_RINGS_SECRET); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_USER_IDS, KEY_RINGS_USER_IDS); @@ -180,9 +175,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_PUBLIC, KEY_RING_PUBLIC); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_SECRET, - KEY_RING_SECRET); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_CERTS, KEY_RING_CERTS); @@ -268,9 +260,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case KEY_RING_USER_IDS: return UserPackets.CONTENT_TYPE; - case KEY_RING_SECRET: - return KeyRings.CONTENT_ITEM_TYPE; - case UPDATED_KEYS: return UpdatedKeys.CONTENT_TYPE; @@ -349,9 +338,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET); projectionMap.put(KeyRings.HAS_ANY_SECRET, - "(EXISTS (SELECT * FROM " + Tables.KEY_RINGS_SECRET + " WHERE " - + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " - + Tables.KEY_RINGS_SECRET + "." + KeyRingData.MASTER_KEY_ID + "(EXISTS (SELECT * FROM " + Tables.KEYS + " AS k WHERE " + + "k." + Keys.HAS_SECRET + " != 0" + + " AND k." + Keys.MASTER_KEY_ID + " = " + + Tables.KEYS + "." + KeyRingData.MASTER_KEY_ID + ")) AS " + KeyRings.HAS_ANY_SECRET); projectionMap.put(KeyRings.HAS_ENCRYPT, "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); @@ -641,24 +631,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case KEY_RINGS_SECRET: - case KEY_RING_SECRET: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id"); - projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); - projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.KEY_RINGS_SECRET); - - if(match == KEY_RING_SECRET) { - qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - } - - break; - } - case KEY_RING_CERTS: case KEY_RING_CERTS_SPECIFIC: case KEY_RING_LINKED_ID_CERTS: { @@ -885,11 +857,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; } - case KEY_RING_SECRET: { - db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values); - keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); - break; - } case KEY_RING_KEYS: { db.insertOrThrow(Tables.KEYS, null, values); keyId = values.getAsLong(Keys.MASTER_KEY_ID); @@ -982,6 +949,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe count = db.delete(Tables.KEY_RINGS_PUBLIC, null, null); break; } + case KEY_RING_PUBLIC: { @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); @@ -992,15 +960,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs); break; } - case KEY_RING_SECRET: { - @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above - String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); - if (!TextUtils.isEmpty(additionalSelection)) { - selection += " AND (" + additionalSelection + ")"; - } - count = db.delete(Tables.KEY_RINGS_SECRET, selection, selectionArgs); - break; - } case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { String packageName = uri.getPathSegments().get(2); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq new file mode 100644 index 000000000..e69de29bb diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiApps.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiApps.sq new file mode 100644 index 000000000..a9f75fc8e --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiApps.sq @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS api_apps ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + package_name TEXT NOT NULL UNIQUE, + package_signature BLOB +); + +getAllowedKeys: +SELECT \ No newline at end of file diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index 6cdc1428d..357960e94 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -244,8 +244,9 @@ public class InteropTest { KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application, LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application), - LastUpdateInteractor.create(RuntimeEnvironment.application), - DatabaseNotifyManager.create(RuntimeEnvironment.application)) { + LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application), + DatabaseNotifyManager.create(RuntimeEnvironment.application), + LastUpdateInteractor.create(RuntimeEnvironment.application)) { @Override public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { From d133b732e576e97fa96e1b9158a0be76f3849c26 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 15 Jun 2018 19:46:55 +0200 Subject: [PATCH 010/124] use SQLDelight, remove ApiApps access from KeychainProvider --- OpenKeychain/build.gradle | 3 + .../keychain/model/ApiAllowedKey.java | 11 + .../keychain/model/ApiApp.java | 33 ++ .../provider/ApiDataAccessObject.java | 218 +++----- .../keychain/provider/KeyRepository.java | 2 +- .../keychain/provider/KeychainContract.java | 40 -- .../keychain/provider/KeychainDatabase.java | 108 ++-- .../keychain/provider/KeychainProvider.java | 144 +----- .../provider/LocalSecretKeyStorage.java | 105 ++++ .../OverriddenWarningsRepository.java | 26 +- .../remote/ApiPendingIntentFactory.java | 4 +- .../keychain/remote/AppSettings.java | 50 -- .../remote/KeychainExternalProvider.java | 33 +- .../keychain/remote/OpenPgpService.java | 3 +- .../remote/PackageUninstallReceiver.java | 8 +- .../remote/SshAuthenticationService.java | 20 +- .../remote/ui/AppSettingsActivity.java | 49 +- .../AppSettingsAllowedKeysListFragment.java | 21 +- .../remote/ui/AppSettingsHeaderFragment.java | 107 ---- .../keychain/remote/ui/AppsListFragment.java | 471 +++++++----------- .../remote/ui/RemoteRegisterPresenter.java | 8 +- .../remote/ui/SelectSignKeyIdActivity.java | 12 +- .../ui/SelectSignKeyIdListFragment.java | 23 +- ...RemoteSelectAuthenticationKeyActivity.java | 23 +- .../RemoteSelectIdentityKeyPresenter.java | 14 +- .../keychain/util/DatabaseUtil.java | 7 +- .../keychain/ApiAllowedKeys.sq | 19 + .../sufficientlysecure/keychain/ApiApps.sq | 22 +- .../remote/KeychainExternalProviderTest.java | 5 +- build.gradle | 1 + 30 files changed, 628 insertions(+), 962 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index e9de55ec9..dca61bd27 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'witness' apply plugin: 'jacoco' +apply plugin: 'com.squareup.sqldelight' // apply plugin: 'com.github.kt3k.coveralls' dependencies { @@ -98,6 +99,8 @@ dependencies { compile "android.arch.lifecycle:extensions:1.0.0" annotationProcessor "android.arch.lifecycle:compiler:1.0.0" + + compile "android.arch.persistence:db-framework:1.0.0" } // Output of ./gradlew -q calculateChecksums diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java new file mode 100644 index 000000000..d635d2931 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.ApiAllowedKeysModel; + + +@AutoValue +public abstract class ApiAllowedKey implements ApiAllowedKeysModel { + public static final Factory FACTORY = new Factory(AutoValue_ApiAllowedKey::new); +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java new file mode 100644 index 000000000..2e84094cb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.ApiAppsModel; + + +@AutoValue +public abstract class ApiApp implements ApiAppsModel { + public static final ApiAppsModel.Factory FACTORY = + new ApiAppsModel.Factory(AutoValue_ApiApp::new); + + public static ApiApp create(String packageName, byte[] packageSignature) { + return new AutoValue_ApiApp(null, packageName, packageSignature); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java index ed33c1863..16082fb56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -20,179 +20,97 @@ package org.sufficientlysecure.keychain.provider; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; -import android.content.ContentResolver; -import android.content.ContentValues; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; -import android.content.OperationApplicationException; import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; -import org.sufficientlysecure.keychain.remote.AppSettings; +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey; +import org.sufficientlysecure.keychain.ApiAppsModel; +import org.sufficientlysecure.keychain.ApiAppsModel.DeleteByPackageName; +import org.sufficientlysecure.keychain.ApiAppsModel.InsertApiApp; +import org.sufficientlysecure.keychain.model.ApiAllowedKey; +import org.sufficientlysecure.keychain.model.ApiApp; public class ApiDataAccessObject { - private final SimpleContentResolverInterface mQueryInterface; + private final SupportSQLiteDatabase db; public ApiDataAccessObject(Context context) { - final ContentResolver contentResolver = context.getContentResolver(); - mQueryInterface = new SimpleContentResolverInterface() { - @Override - public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Uri insert(Uri contentUri, ContentValues values) { - return contentResolver.insert(contentUri, values); - } - - @Override - public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) { - return contentResolver.update(contentUri, values, where, selectionArgs); - } - - @Override - public int delete(Uri contentUri, String where, String[] selectionArgs) { - return contentResolver.delete(contentUri, where, selectionArgs); - } - }; + KeychainDatabase keychainDatabase = new KeychainDatabase(context); + db = keychainDatabase.getWritableDatabase(); } - public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) { - mQueryInterface = queryInterface; - } - - public ArrayList getRegisteredApiApps() { - Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null); - - ArrayList packageNames = new ArrayList<>(); - try { - if (cursor != null) { - int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); - if (cursor.moveToFirst()) { - do { - packageNames.add(cursor.getString(packageNameCol)); - } while (cursor.moveToNext()); - } - } - } finally { - if (cursor != null) { - cursor.close(); + public ApiApp getApiApp(String packageName) { + try (Cursor cursor = db.query(ApiApp.FACTORY.selectByPackageName(packageName))) { + if (cursor.moveToFirst()) { + return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor); } + return null; } - - return packageNames; - } - - private ContentValues contentValueForApiApps(AppSettings appSettings) { - ContentValues values = new ContentValues(); - values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); - values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); - return values; - } - - public void insertApiApp(AppSettings appSettings) { - mQueryInterface.insert(ApiApps.CONTENT_URI, contentValueForApiApps(appSettings)); - } - - public void deleteApiApp(String packageName) { - mQueryInterface.delete(ApiApps.buildByPackageNameUri(packageName), null, null); - } - - /** - * Must be an uri pointing to an account - */ - public AppSettings getApiAppSettings(Uri uri) { - AppSettings settings = null; - - Cursor cursor = mQueryInterface.query(uri, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - settings = new AppSettings(); - settings.setPackageName(cursor.getString( - cursor.getColumnIndex(ApiApps.PACKAGE_NAME))); - settings.setPackageCertificate(cursor.getBlob( - cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE))); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return settings; - } - - public HashSet getAllowedKeyIdsForApp(Uri uri) { - HashSet keyIds = new HashSet<>(); - - Cursor cursor = mQueryInterface.query(uri, null, null, null, null); - try { - if (cursor != null) { - int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID); - while (cursor.moveToNext()) { - keyIds.add(cursor.getLong(keyIdColumn)); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return keyIds; - } - - public void saveAllowedKeyIdsForApp(Uri uri, Set allowedKeyIds) - throws RemoteException, OperationApplicationException { - // wipe whole table of allowed keys for this account - mQueryInterface.delete(uri, null, null); - - // re-insert allowed key ids - for (Long keyId : allowedKeyIds) { - ContentValues values = new ContentValues(); - values.put(ApiAllowedKeys.KEY_ID, keyId); - mQueryInterface.insert(uri, values); - } - } - - public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) { - ContentValues values = new ContentValues(); - values.put(ApiAllowedKeys.KEY_ID, allowedKeyId); - mQueryInterface.insert(uri, values); - } - - public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) { - Uri uri = ApiAllowedKeys.buildBaseUri(packageName); - addAllowedKeyIdForApp(uri, allowedKeyId); } public byte[] getApiAppCertificate(String packageName) { - Uri queryUri = ApiApps.buildByPackageNameUri(packageName); + Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName)); + return ApiApp.FACTORY.getCertificateMapper().map(cursor); + } - String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; + public void insertApiApp(ApiApp apiApp) { + InsertApiApp statement = new ApiAppsModel.InsertApiApp(db); + statement.bind(apiApp.package_name(), apiApp.package_signature()); + statement.execute(); + } - Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null); - try { - byte[] signature = null; - if (cursor != null && cursor.moveToFirst()) { - int signatureCol = 0; + public void deleteApiApp(String packageName) { + DeleteByPackageName deleteByPackageName = new DeleteByPackageName(db); + deleteByPackageName.bind(packageName); + deleteByPackageName.executeUpdateDelete(); + } - signature = cursor.getBlob(signatureCol); - } - return signature; - } finally { - if (cursor != null) { - cursor.close(); + public HashSet getAllowedKeyIdsForApp(String packageName) { + SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName); + HashSet keyIds = new HashSet<>(); + try (Cursor cursor = db.query(allowedKeys)) { + while (cursor.moveToNext()) { + long allowedKeyId = ApiAllowedKey.FACTORY.getAllowedKeysMapper().map(cursor); + keyIds.add(allowedKeyId); } } + return keyIds; + } + + public void saveAllowedKeyIdsForApp(String packageName, Set allowedKeyIds) { + ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(db); + deleteByPackageName.bind(packageName); + deleteByPackageName.executeUpdateDelete(); + + InsertAllowedKey statement = new InsertAllowedKey(db); + for (Long keyId : allowedKeyIds) { + statement.bind(packageName, keyId); + statement.execute(); + } } + public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) { + InsertAllowedKey statement = new InsertAllowedKey(db); + statement.bind(packageName, allowedKeyId); + statement.execute(); + } + + public List getAllApiApps() { + SqlDelightQuery query = ApiApp.FACTORY.selectAll(); + + ArrayList result = new ArrayList<>(); + try (Cursor cursor = db.query(query)) { + while (cursor.moveToNext()) { + ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor); + result.add(apiApp); + } + } + return result; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index e6dcff07d..f0afe66ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -333,7 +333,7 @@ public class KeyRepository { try { return localSecretKeyStorage.readSecretKey(masterKeyId); } catch (IOException e) { - Timber.e(e, "Error reading public key from storage!"); + Timber.e(e, "Error reading secret key from storage!"); throw new NotFoundException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 85d88d4fc..a47583170 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -141,9 +141,6 @@ public class KeychainContract { public static final String PATH_KEYS = "keys"; public static final String PATH_CERTS = "certs"; - public static final String BASE_API_APPS = "api_apps"; - public static final String PATH_ALLOWED_KEYS = "allowed_keys"; - public static final String PATH_BY_PACKAGE_NAME = "by_package_name"; public static final String PATH_BY_KEY_ID = "by_key_id"; @@ -323,43 +320,6 @@ public class KeychainContract { } - public static class ApiApps implements ApiAppsColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_API_APPS).build(); - - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps"; - - /** - * Use if a single item is returned - */ - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.api_apps"; - - public static Uri buildByPackageNameUri(String packageName) { - return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); - } - } - - public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_API_APPS).build(); - - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys"; - - public static Uri buildBaseUri(String packageName) { - return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS) - .build(); - } - } - public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_AUTOCRYPT_PEERS).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 25d0f0743..e35b6a85d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -23,16 +23,18 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteOpenHelper; +import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback; +import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration; +import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory; import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; @@ -53,10 +55,11 @@ import timber.log.Timber; * - TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE). * - BLOB. The value is a blob of data, stored exactly as it was input. */ -public class KeychainDatabase extends SQLiteOpenHelper { +public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; private static final int DATABASE_VERSION = 26; - private Context mContext; + private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; + private Context context; public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; @@ -65,18 +68,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { String KEY_SIGNATURES = "key_signatures"; String USER_PACKETS = "user_packets"; String CERTS = "certs"; - String API_APPS = "api_apps"; String API_ALLOWED_KEYS = "api_allowed_keys"; String OVERRIDDEN_WARNINGS = "overridden_warnings"; String API_AUTOCRYPT_PEERS = "api_autocrypt_peers"; } - private static final String CREATE_KEYRINGS_PUBLIC = - "CREATE TABLE IF NOT EXISTS keyrings_public (" - + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," - + KeyRingsColumns.KEY_RING_DATA + " BLOB" - + ")"; - private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " (" + KeysColumns.MASTER_KEY_ID + " INTEGER, " @@ -143,15 +139,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE" + ")"; - private static final String CREATE_UPDATE_KEYS = - "CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " (" - + UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, " - + UpdatedKeysColumns.LAST_UPDATED + " INTEGER, " - + UpdatedKeysColumns.SEEN_ON_KEYSERVERS + " INTEGER, " - + "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" - + ")"; - private static final String CREATE_KEY_SIGNATURES = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " (" + KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " @@ -175,16 +162,9 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", " + ApiAutocryptPeerColumns.IDENTIFIER + "), " + "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES " - + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + "api_apps (package_signature) ON DELETE CASCADE" + ")"; - private static final String CREATE_API_APPS = - "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, " - + ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB" - + ")"; - private static final String CREATE_API_APPS_ALLOWED_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " @@ -194,7 +174,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", " + ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), " + "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES " - + Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + "api_apps (" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + ")"; private static final String CREATE_OVERRIDDEN_WARNINGS = @@ -204,12 +184,46 @@ public class KeychainDatabase extends SQLiteOpenHelper { + ")"; public KeychainDatabase(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - mContext = context; + this.context = context; + supportSQLiteOpenHelper = + new FrameworkSQLiteOpenHelperFactory() + .create(Configuration.builder(context).name(DATABASE_NAME).callback( + new Callback(DATABASE_VERSION) { + @Override + public void onCreate(SupportSQLiteDatabase db) { + KeychainDatabase.this.onCreate(db); + } + + @Override + public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + KeychainDatabase.this.onUpgrade(db, oldVersion, newVersion); + } + + @Override + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + KeychainDatabase.this.onDowngrade(db, oldVersion, newVersion); + } + + @Override + public void onOpen(SupportSQLiteDatabase db) { + super.onOpen(db); + if (!db.isReadOnly()) { + // Enable foreign key constraints + db.execSQL("PRAGMA foreign_keys=ON;"); + } + } + }).build()); } - @Override - public void onCreate(SQLiteDatabase db) { + public SupportSQLiteDatabase getReadableDatabase() { + return supportSQLiteOpenHelper.getReadableDatabase(); + } + + public SupportSQLiteDatabase getWritableDatabase() { + return supportSQLiteOpenHelper.getWritableDatabase(); + } + + private void onCreate(SupportSQLiteDatabase db) { Timber.w("Creating database..."); db.execSQL(CREATE_KEYRINGS_PUBLIC); @@ -218,7 +232,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_CERTS); db.execSQL(CREATE_UPDATE_KEYS); db.execSQL(CREATE_KEY_SIGNATURES); - db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(CREATE_API_AUTOCRYPT_PEERS); @@ -231,20 +244,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("CREATE INDEX uids_by_email ON user_packets (" + UserPacketsColumns.EMAIL + ");"); - Preferences.getPreferences(mContext).setKeySignaturesTableInitialized(); + Preferences.getPreferences(context).setKeySignaturesTableInitialized(); } - @Override - public void onOpen(SQLiteDatabase db) { - super.onOpen(db); - if (!db.isReadOnly()) { - // Enable foreign key constraints - db.execSQL("PRAGMA foreign_keys=ON;"); - } - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + private void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { Timber.d("Upgrading db from " + oldVersion + " to " + newVersion); switch (oldVersion) { @@ -459,9 +462,9 @@ public class KeychainDatabase extends SQLiteOpenHelper { } } - private void migrateSecretKeysFromDbToLocalStorage(SQLiteDatabase db) throws IOException { - LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(mContext); - Cursor cursor = db.rawQuery("SELECT master_key_id, key_ring_data FROM keyrings_secret", null); + private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException { + LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); + Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret"); while (cursor.moveToNext()) { long masterKeyId = cursor.getLong(0); byte[] secretKeyBlob = cursor.getBlob(1); @@ -473,8 +476,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { // db.execSQL("DROP TABLE keyrings_secret"); } - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { // Downgrade is ok for the debug version, makes it easier to work with branches if (Constants.DEBUG) { return; @@ -528,7 +530,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { public void clearDatabase() { getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC); getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS); - getWritableDatabase().execSQL("delete from " + Tables.API_APPS); + getWritableDatabase().execSQL("delete from api_apps"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 1d7e39b6a..1f64a9a49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -23,6 +23,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; @@ -38,8 +39,6 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; @@ -71,10 +70,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RING_LINKED_IDS = 207; private static final int KEY_RING_LINKED_ID_CERTS = 208; - private static final int API_APPS = 301; - private static final int API_APPS_BY_PACKAGE_NAME = 302; - private static final int API_ALLOWED_KEYS = 305; - private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; private static final int KEY_RINGS_FIND_BY_USER_ID = 402; @@ -182,22 +177,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe + KeychainContract.PATH_CERTS + "/*/*", KEY_RING_CERTS_SPECIFIC); - /* - * API apps - * - *
-         * api_apps
-         * api_apps/_ (package name)
-         *
-         * api_apps/_/allowed_keys
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); - matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); - - matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" - + KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS); - /* * Trust Identity access * @@ -269,15 +248,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case KEY_SIGNATURES: return KeySignatures.CONTENT_TYPE; - case API_APPS: - return ApiApps.CONTENT_TYPE; - - case API_APPS_BY_PACKAGE_NAME: - return ApiApps.CONTENT_ITEM_TYPE; - - case API_ALLOWED_KEYS: - return ApiAllowedKeys.CONTENT_TYPE; - default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -781,26 +751,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } break; } - - case API_APPS: { - qb.setTables(Tables.API_APPS); - - break; - } - case API_APPS_BY_PACKAGE_NAME: { - qb.setTables(Tables.API_APPS); - qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); - qb.appendWhereEscapeString(uri.getLastPathSegment()); - - break; - } - case API_ALLOWED_KEYS: { - qb.setTables(Tables.API_ALLOWED_KEYS); - qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAllowedKeys.PACKAGE_NAME + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - - break; - } default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -815,9 +765,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe orderBy = sortOrder; } - SQLiteDatabase db = getDb().getReadableDatabase(); + SupportSQLiteDatabase db = getDb().getReadableDatabase(); - Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy); + String query = qb.buildQuery(projection, selection, groupBy, null, orderBy, null); + Cursor cursor = db.query(query, selectionArgs); if (cursor != null) { // Tell the cursor what uri to watch, so it knows when its source data changes cursor.setNotificationUri(getContext().getContentResolver(), uri); @@ -844,7 +795,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public Uri insert(Uri uri, ContentValues values) { Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")"); - final SQLiteDatabase db = getDb().getWritableDatabase(); + final SupportSQLiteDatabase db = getDb().getWritableDatabase(); Uri rowUri = null; Long keyId = null; @@ -853,12 +804,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe switch (match) { case KEY_RING_PUBLIC: { - db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values); + db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; } case KEY_RING_KEYS: { - db.insertOrThrow(Tables.KEYS, null, values); + db.insert(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(Keys.MASTER_KEY_ID); break; } @@ -873,46 +824,33 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) { throw new AssertionError("Rank 0 user packet must be a user id!"); } - db.insertOrThrow(Tables.USER_PACKETS, null, values); + db.insert(Tables.USER_PACKETS, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(UserPackets.MASTER_KEY_ID); break; } case KEY_RING_CERTS: { // we replace here, keeping only the latest signature // TODO this would be better handled in savePublicKeyRing directly! - db.replaceOrThrow(Tables.CERTS, null, values); + db.insert(Tables.CERTS, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(Certs.MASTER_KEY_ID); break; } case UPDATED_KEYS: { keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID); try { - db.insertOrThrow(Tables.UPDATED_KEYS, null, values); + db.insert(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values); } catch (SQLiteConstraintException e) { - db.update(Tables.UPDATED_KEYS, values, + db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_IGNORE, values, UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(keyId) }); } rowUri = UpdatedKeys.CONTENT_URI; break; } case KEY_SIGNATURES: { - db.insert(Tables.KEY_SIGNATURES, null, values); + db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values); rowUri = KeySignatures.CONTENT_URI; break; } - case API_APPS: { - db.insert(Tables.API_APPS, null, values); - break; - } - case API_ALLOWED_KEYS: { - // set foreign key automatically based on given uri - // e.g., api_apps/com.example.app/allowed_keys/ - String packageName = uri.getPathSegments().get(1); - values.put(ApiAllowedKeys.PACKAGE_NAME, packageName); - - db.insert(Tables.API_ALLOWED_KEYS, null, values); - break; - } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -937,7 +875,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public int delete(Uri uri, String additionalSelection, String[] selectionArgs) { Timber.v("delete(uri=" + uri + ")"); - final SQLiteDatabase db = getDb().getWritableDatabase(); + final SupportSQLiteDatabase db = getDb().getWritableDatabase(); int count; final int match = mUriMatcher.match(uri); @@ -980,16 +918,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs); break; - case API_APPS_BY_PACKAGE_NAME: { - count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection), - selectionArgs); - break; - } - case API_ALLOWED_KEYS: { - count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection), - selectionArgs); - break; - } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -1005,7 +933,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")"); - final SQLiteDatabase db = getDb().getWritableDatabase(); + final SupportSQLiteDatabase db = getDb().getWritableDatabase(); int count = 0; try { @@ -1022,12 +950,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe if (!TextUtils.isEmpty(selection)) { actualSelection += " AND (" + selection + ")"; } - count = db.update(Tables.KEYS, values, actualSelection, selectionArgs); - break; - } - case API_APPS_BY_PACKAGE_NAME: { - count = db.update(Tables.API_APPS, values, - buildDefaultApiAppsSelection(uri, selection), selectionArgs); + count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs); break; } case UPDATED_KEYS: { @@ -1040,7 +963,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe throw new UnsupportedOperationException("can only reset all keys"); } - db.update(Tables.UPDATED_KEYS, values, null, null); + db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values, null, null); break; } case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { @@ -1049,11 +972,11 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); values.put(ApiAutocryptPeer.IDENTIFIER, identifier); - int updated = db.update(Tables.API_AUTOCRYPT_PEERS, values, + int updated = db.update(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_IGNORE, values, ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?", new String[] { packageName, identifier }); if (updated == 0) { - db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values); + db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values); } break; @@ -1068,35 +991,4 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe return count; } - - /** - * Build default selection statement for API apps. If no extra selection is specified only build - * where clause with rowId - * - * @param uri - * @param selection - * @return - */ - private String buildDefaultApiAppsSelection(Uri uri, String selection) { - String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment()); - - String andSelection = ""; - if (!TextUtils.isEmpty(selection)) { - andSelection = " AND (" + selection + ")"; - } - - return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection; - } - - private String buildDefaultApiAllowedKeysSelection(Uri uri, String selection) { - String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1)); - - String andSelection = ""; - if (!TextUtils.isEmpty(selection)) { - andSelection = " AND (" + selection + ")"; - } - - return ApiAllowedKeys.PACKAGE_NAME + "=" + packageName + andSelection; - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java new file mode 100644 index 000000000..cda09fde3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.provider; + + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +import android.content.Context; + +import okhttp3.internal.Util; + + +class LocalSecretKeyStorage { + private static final String FORMAT_STR_SECRET_KEY = "0x%016x.sec"; + private static final String SECRET_KEYS_DIR_NAME = "secret_keys"; + + + private final File localSecretKeysDir; + + + public static LocalSecretKeyStorage getInstance(Context context) { + File localSecretKeysDir = new File(context.getFilesDir(), SECRET_KEYS_DIR_NAME); + return new LocalSecretKeyStorage(localSecretKeysDir); + } + + private LocalSecretKeyStorage(File localSecretKeysDir) { + this.localSecretKeysDir = localSecretKeysDir; + } + + private File getSecretKeyFile(long masterKeyId) throws IOException { + if (!localSecretKeysDir.exists()) { + localSecretKeysDir.mkdir(); + } + if (!localSecretKeysDir.isDirectory()) { + throw new IOException("Failed creating public key directory!"); + } + + String keyFilename = String.format(FORMAT_STR_SECRET_KEY, masterKeyId); + return new File(localSecretKeysDir, keyFilename); + } + + void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException { + File publicKeyFile = getSecretKeyFile(masterKeyId); + + FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile); + try { + fileOutputStream.write(encoded); + } finally { + Util.closeQuietly(fileOutputStream); + } + } + + byte[] readSecretKey(long masterKeyId) throws IOException { + File publicKeyFile = getSecretKeyFile(masterKeyId); + + try { + FileInputStream fileInputStream = new FileInputStream(publicKeyFile); + return readIntoByteArray(fileInputStream); + } catch (FileNotFoundException e) { + return null; + } + } + + private static byte[] readIntoByteArray(FileInputStream fileInputStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + byte[] buf = new byte[128]; + int bytesRead; + while ((bytesRead = fileInputStream.read(buf)) != -1) { + baos.write(buf, 0, bytesRead); + } + + return baos.toByteArray(); + } + + void deleteSecretKey(long masterKeyId) throws IOException { + File publicKeyFile = getSecretKeyFile(masterKeyId); + if (publicKeyFile.exists()) { + boolean deleteSuccess = publicKeyFile.delete(); + if (!deleteSuccess) { + throw new IOException("File exists, but could not be deleted!"); + } + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java index 4405363fe..fe1b23ca5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.provider; +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteQuery; +import android.arch.persistence.db.SupportSQLiteQueryBuilder; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -47,34 +50,31 @@ public class OverriddenWarningsRepository { } public boolean isWarningOverridden(String identifier) { - SQLiteDatabase db = getDb().getReadableDatabase(); - Cursor cursor = db.query( - Tables.OVERRIDDEN_WARNINGS, - new String[] { "COUNT(*)" }, - OverriddenWarnings.IDENTIFIER + " = ?", - new String[] { identifier }, - null, null, null); + SupportSQLiteDatabase db = getDb().getReadableDatabase(); + SupportSQLiteQuery query = SupportSQLiteQueryBuilder + .builder(Tables.OVERRIDDEN_WARNINGS) + .columns(new String[] { "COUNT(*) FROM " }) + .selection(OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }) + .create(); + Cursor cursor = db.query(query); try { cursor.moveToFirst(); return cursor.getInt(0) > 0; } finally { cursor.close(); - db.close(); } } public void putOverride(String identifier) { - SQLiteDatabase db = getDb().getWritableDatabase(); + SupportSQLiteDatabase db = getDb().getWritableDatabase(); ContentValues cv = new ContentValues(); cv.put(OverriddenWarnings.IDENTIFIER, identifier); - db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv); - db.close(); + db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv); } public void deleteOverride(String identifier) { - SQLiteDatabase db = getDb().getWritableDatabase(); + SupportSQLiteDatabase db = getDb().getWritableDatabase(); db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }); - db.close(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 18a467299..4f135f14f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -138,7 +138,7 @@ public class ApiPendingIntentFactory { PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) { Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName)); + intent.putExtra(SelectSignKeyIdActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId); return createInternal(data, intent); @@ -147,7 +147,6 @@ public class ApiPendingIntentFactory { PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName, byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) { Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName)); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId); @@ -158,7 +157,6 @@ public class ApiPendingIntentFactory { PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) { Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName)); intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName); return createInternal(data, intent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java deleted file mode 100644 index a5dcc24bd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote; - -public class AppSettings { - private String mPackageName; - private byte[] mPackageCertificate; - - public AppSettings() { - - } - - public AppSettings(String packageName, byte[] packageSignature) { - super(); - this.mPackageName = packageName; - this.mPackageCertificate = packageSignature; - } - - public String getPackageName() { - return mPackageName; - } - - public void setPackageName(String packageName) { - this.mPackageName = packageName; - } - - public byte[] getPackageCertificate() { - return mPackageCertificate; - } - - public void setPackageCertificate(byte[] packageCertificate) { - this.mPackageCertificate = packageCertificate; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 2253e3e4d..855babaaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; @@ -42,7 +43,6 @@ import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState; import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; @@ -54,7 +54,6 @@ import org.sufficientlysecure.keychain.provider.KeychainExternalContract.Autocry import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.provider.KeychainProvider; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; -import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory; import timber.log.Timber; @@ -64,9 +63,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC private static final int AUTOCRYPT_STATUS = 201; private static final int AUTOCRYPT_STATUS_INTERNAL = 202; - private static final int API_APPS = 301; - private static final int API_APPS_BY_PACKAGE_NAME = 302; - public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses"; public static final String TEMP_TABLE_COLUMN_ADDRES = "address"; @@ -113,7 +109,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC internalKeychainProvider = new KeychainProvider(); internalKeychainProvider.attachInfo(context, null); - apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider)); + apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext())); databaseNotifyManager = DatabaseNotifyManager.create(context); return true; } @@ -127,13 +123,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC switch (match) { case EMAIL_STATUS: return EmailStatus.CONTENT_TYPE; - - case API_APPS: - return ApiApps.CONTENT_TYPE; - - case API_APPS_BY_PACKAGE_NAME: - return ApiApps.CONTENT_ITEM_TYPE; - default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -154,7 +143,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC String groupBy = null; - SQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase(); + SupportSQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase(); String callingPackageName = apiPermissionHelper.getCurrentCallingPackage(); @@ -169,7 +158,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC ContentValues cv = new ContentValues(); for (String address : selectionArgs) { cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv); + db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); } HashMap projectionMap = new HashMap<>(); @@ -256,7 +245,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC ContentValues cv = new ContentValues(); for (String address : selectionArgs) { cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv); + db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); } boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%"); @@ -321,8 +310,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC } qb.setStrict(true); - qb.setCursorFactory(new CloseDatabaseCursorFactory()); - Cursor cursor = qb.query(db, projection, null, null, groupBy, null, orderBy); + String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy); + Cursor cursor = db.query(query); if (cursor != null) { // Tell the cursor what uri to watch, so it knows when its source data changes cursor.setNotificationUri(getContext().getContentResolver(), uri); @@ -337,7 +326,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC return cursor; } - private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db, + private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) { List autocryptStates = autocryptPeerDao.determineAutocryptRecommendations(peerIds); @@ -345,7 +334,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC fillTempTableWithAutocryptRecommendations(db, autocryptStates); } - private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db, + private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, List autocryptRecommendations) { ContentValues cv = new ContentValues(); for (AutocryptRecommendationResult peerResult : autocryptRecommendations) { @@ -359,12 +348,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC KeychainExternalContract.KEY_STATUS_UNVERIFIED); } - db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", + db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", new String[] { peerResult.peerId }); } } - private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) { + private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) { String cmpOperator = isWildcardSelector ? " LIKE " : " = "; long unixSeconds = System.currentTimeMillis() / 1000; db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 6b1ea3ef6..8406c297b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -915,8 +915,7 @@ public class OpenPgpService extends Service { private HashSet getAllowedKeyIds() { String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - return mApiDao.getAllowedKeyIdsForApp( - KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + return mApiDao.getAllowedKeyIdsForApp(currentPkg); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java index 338807ec0..85c6b2dad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java @@ -17,12 +17,13 @@ package org.sufficientlysecure.keychain.remote; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; public class PackageUninstallReceiver extends BroadcastReceiver { @@ -34,8 +35,9 @@ public class PackageUninstallReceiver extends BroadcastReceiver { return; } String packageName = uri.getEncodedSchemeSpecificPart(); - Uri appUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName); - context.getContentResolver().delete(appUri, null, null); + + ApiDataAccessObject apiDao = new ApiDataAccessObject(context); + apiDao.deleteApiApp(packageName); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java index 57dab705e..aead4985c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java @@ -17,11 +17,19 @@ package org.sufficientlysecure.keychain.remote; + +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; -import android.util.Log; + import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.openintents.ssh.authentication.ISshAuthenticationService; @@ -40,7 +48,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ssh.AuthenticationData; @@ -50,13 +57,6 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult; import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter; import timber.log.Timber; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; - public class SshAuthenticationService extends Service { private static final String TAG = "SshAuthService"; @@ -394,7 +394,7 @@ public class SshAuthenticationService extends Service { private HashSet getAllowedKeyIds() { String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - return mApiDao.getAllowedKeyIdsForApp(KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + return mApiDao.getAllowedKeyIdsForApp(currentPkg); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index a54da0fb7..79c54147e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.view.Menu; @@ -36,24 +35,26 @@ import android.widget.TextView; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import timber.log.Timber; public class AppSettingsActivity extends BaseActivity { - private Uri mAppUri; + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + private String packageName; private TextView mAppNameView; private ImageView mAppIconView; // model - AppSettings mAppSettings; + ApiApp mApiApp; + private ApiDataAccessObject apiDataAccessObject; @Override protected void onCreate(Bundle savedInstanceState) { @@ -68,15 +69,16 @@ public class AppSettingsActivity extends BaseActivity { setTitle(null); Intent intent = getIntent(); - mAppUri = intent.getData(); - if (mAppUri == null) { - Timber.e("Intent data missing. Should be Uri of app!"); + packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + if (packageName == null) { + Timber.e("Required extra package_name missing!"); finish(); return; } - Timber.d("uri: %s", mAppUri); - loadData(savedInstanceState, mAppUri); + apiDataAccessObject = new ApiDataAccessObject(this); + + loadData(savedInstanceState); } private void save() { @@ -138,7 +140,7 @@ public class AppSettingsActivity extends BaseActivity { // advanced info: package certificate SHA-256 try { MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(mAppSettings.getPackageCertificate()); + md.update(mApiApp.package_signature()); byte[] digest = md.digest(); certificate = new String(Hex.encode(digest)); } catch (NoSuchAlgorithmException e) { @@ -146,7 +148,7 @@ public class AppSettingsActivity extends BaseActivity { } AdvancedAppSettingsDialogFragment dialogFragment = - AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate); + AdvancedAppSettingsDialogFragment.newInstance(mApiApp.package_name(), certificate); dialogFragment.show(getSupportFragmentManager(), "advancedDialog"); } @@ -155,7 +157,7 @@ public class AppSettingsActivity extends BaseActivity { Intent i; PackageManager manager = getPackageManager(); try { - i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName()); + i = manager.getLaunchIntentForPackage(mApiApp.package_name()); if (i == null) throw new PackageManager.NameNotFoundException(); // start like the Android launcher would do @@ -167,31 +169,29 @@ public class AppSettingsActivity extends BaseActivity { } } - private void loadData(Bundle savedInstanceState, Uri appUri) { - mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri); + private void loadData(Bundle savedInstanceState) { + mApiApp = apiDataAccessObject.getApiApp(packageName); // get application name and icon from package manager String appName; Drawable appIcon = null; PackageManager pm = getApplicationContext().getPackageManager(); try { - ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0); + ApplicationInfo ai = pm.getApplicationInfo(mApiApp.package_name(), 0); appName = (String) pm.getApplicationLabel(ai); appIcon = pm.getApplicationIcon(ai); } catch (PackageManager.NameNotFoundException e) { // fallback - appName = mAppSettings.getPackageName(); + appName = mApiApp.package_name(); } mAppNameView.setText(appName); mAppIconView.setImageDrawable(appIcon); - Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); - Timber.d("allowedKeysUri: " + allowedKeysUri); - startListFragments(savedInstanceState, allowedKeysUri); + startListFragments(savedInstanceState); } - private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) { + private void startListFragments(Bundle savedInstanceState) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -199,7 +199,8 @@ public class AppSettingsActivity extends BaseActivity { return; } - AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri); + // Create an instance of the fragments + AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(packageName); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() @@ -210,9 +211,7 @@ public class AppSettingsActivity extends BaseActivity { } private void revokeAccess() { - if (getContentResolver().delete(mAppUri, null, null) <= 0) { - throw new RuntimeException(); - } + apiDataAccessObject.deleteApiApp(packageName); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index fd4f37525..5c8b99926 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.remote.ui; import java.util.Set; -import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.RemoteException; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -40,25 +38,24 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter; import org.sufficientlysecure.keychain.ui.widget.FixedListView; -import timber.log.Timber; public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { - private static final String ARG_DATA_URI = "uri"; + private static final String ARG_PACKAGE_NAME = "package_name"; private KeySelectableAdapter mAdapter; private ApiDataAccessObject mApiDao; - private Uri mDataUri; + private String packageName; /** * Creates new instance of this fragment */ - public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) { + public static AppSettingsAllowedKeysListFragment newInstance(String packageName) { AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); + args.putString(ARG_PACKAGE_NAME, packageName); frag.setArguments(args); @@ -101,13 +98,13 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mDataUri = getArguments().getParcelable(ARG_DATA_URI); + packageName = getArguments().getString(ARG_PACKAGE_NAME); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - Set checked = mApiDao.getAllowedKeyIdsForApp(mDataUri); + Set checked = mApiDao.getAllowedKeyIdsForApp(packageName); mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked); setListAdapter(mAdapter); getListView().setOnItemClickListener(mAdapter); @@ -140,11 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i } */ public void saveAllowedKeys() { - try { - mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); - } catch (RemoteException | OperationApplicationException e) { - Timber.e(e, "Problem saving allowed key ids!"); - } + mApiDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java deleted file mode 100644 index dc9b4d487..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.remote.AppSettings; -import timber.log.Timber; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class AppSettingsHeaderFragment extends Fragment { - - // model - private AppSettings mAppSettings; - - // view - private TextView mAppNameView; - private ImageView mAppIconView; - private TextView mPackageName; - private TextView mPackageCertificate; - - public AppSettings getAppSettings() { - return mAppSettings; - } - - public void setAppSettings(AppSettings appSettings) { - this.mAppSettings = appSettings; - updateView(appSettings); - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false); - mAppNameView = view.findViewById(R.id.api_app_settings_app_name); - mAppIconView = view.findViewById(R.id.api_app_settings_app_icon); - mPackageName = view.findViewById(R.id.api_app_settings_package_name); - mPackageCertificate = view.findViewById(R.id.api_app_settings_package_certificate); - return view; - } - - private void updateView(AppSettings appSettings) { - // get application name and icon from package manager - String appName; - Drawable appIcon = null; - PackageManager pm = getActivity().getApplicationContext().getPackageManager(); - try { - ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0); - - appName = (String) pm.getApplicationLabel(ai); - appIcon = pm.getApplicationIcon(ai); - } catch (NameNotFoundException e) { - // fallback - appName = appSettings.getPackageName(); - } - mAppNameView.setText(appName); - mAppIconView.setImageDrawable(appIcon); - - // advanced info: package name - mPackageName.setText(appSettings.getPackageName()); - - // advanced info: package signature SHA-256 - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(appSettings.getPackageCertificate()); - byte[] digest = md.digest(); - String signature = new String(Hex.encode(digest)); - - mPackageCertificate.setText(signature); - } catch (NoSuchAlgorithmException e) { - Timber.e(e, "Should not happen!"); - } - } - - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index 6410d8f10..e55b1e19f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -17,87 +17,77 @@ package org.sufficientlysecure.keychain.remote.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.CursorJoiner; -import android.database.MatrixCursor; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.ListFragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v4.widget.CursorAdapter; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter; +import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; +import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; import timber.log.Timber; -public class AppsListFragment extends ListFragment implements - LoaderManager.LoaderCallbacks, OnItemClickListener { - - AppsAdapter mAdapter; +public class AppsListFragment extends RecyclerFragment { + private ApiAppAdapter adapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getListView().setOnItemClickListener(this); - - // NOTE: No setEmptyText(), we always have the default entries - - // We have a menu item to show in action bar. setHasOptionsMenu(true); - // Create an empty adapter we will use to display the loaded data. - mAdapter = new AppsAdapter(getActivity(), null, 0); - setListAdapter(mAdapter); + adapter = new ApiAppAdapter(getActivity()); + setAdapter(adapter); + setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); - // NOTE: Loader is started in onResume! + new ApiAppsLiveData(getContext()).observe(this, this::onLoad); } - @Override - public void onResume() { - super.onResume(); - - // Start out with a progress indicator. - setListShown(false); - - // After coming back from Google Play -> reload - getLoaderManager().restartLoader(0, null, this); + private void onLoad(List apiApps) { + if (apiApps == null) { + hideList(false); + adapter.setData(null); + return; + } + adapter.setData(apiApps); + showList(true); } - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - String selectedPackageName = mAdapter.getItemPackageName(position); - boolean installed = mAdapter.getItemIsInstalled(position); - boolean registered = mAdapter.getItemIsRegistered(position); + public void onItemClick(int position) { + ListedApp listedApp = adapter.data.get(position); - if (installed) { - if (registered) { + if (listedApp.isInstalled) { + if (listedApp.isRegistered) { // Edit app settings Intent intent = new Intent(getActivity(), AppSettingsActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName)); + intent.putExtra(AppSettingsActivity.EXTRA_PACKAGE_NAME, listedApp.packageName); startActivity(intent); } else { Intent i; PackageManager manager = getActivity().getPackageManager(); try { - i = manager.getLaunchIntentForPackage(selectedPackageName); + i = manager.getLaunchIntentForPackage(listedApp.packageName); if (i == null) { throw new PackageManager.NameNotFoundException(); } @@ -112,256 +102,163 @@ public class AppsListFragment extends ListFragment implements } else { try { startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=" + selectedPackageName))); + Uri.parse("market://details?id=" + listedApp.packageName))); } catch (ActivityNotFoundException anfe) { startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName))); + Uri.parse("https://play.google.com/store/apps/details?id=" + listedApp.packageName))); } } } - private static final String TEMP_COLUMN_NAME = "NAME"; - private static final String TEMP_COLUMN_INSTALLED = "INSTALLED"; - private static final String TEMP_COLUMN_REGISTERED = "REGISTERED"; - private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID"; + public class ApiAppAdapter extends Adapter { + private final LayoutInflater inflater; - static final String[] PROJECTION = new String[]{ - ApiApps._ID, // 0 - ApiApps.PACKAGE_NAME, // 1 - "null as " + TEMP_COLUMN_NAME, // installed apps can retrieve app name from Android OS - "0 as " + TEMP_COLUMN_INSTALLED, // changed later in cursor joiner - "1 as " + TEMP_COLUMN_REGISTERED, // if it is in db it is registered - "0 as " + TEMP_COLUMN_ICON_RES_ID // not used + private List data; + + ApiAppAdapter(Context context) { + super(); + + inflater = LayoutInflater.from(context); + } + + @Override + public ApiAppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ApiAppViewHolder(inflater.inflate(R.layout.api_apps_adapter_list_item, parent, false)); + } + + @Override + public void onBindViewHolder(ApiAppViewHolder holder, int position) { + ListedApp item = data.get(position); + holder.bind(item); + } + + @Override + public int getItemCount() { + return data != null ? data.size() : 0; + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + } + + public class ApiAppViewHolder extends RecyclerView.ViewHolder { + private final TextView text; + private final ImageView icon; + private final ImageView installIcon; + + ApiAppViewHolder(View itemView) { + super(itemView); + + text = itemView.findViewById(R.id.api_apps_adapter_item_name); + icon = itemView.findViewById(R.id.api_apps_adapter_item_icon); + installIcon = itemView.findViewById(R.id.api_apps_adapter_install_icon); + itemView.setOnClickListener((View view) -> onItemClick(getAdapterPosition())); + } + + void bind(ListedApp listedApp) { + text.setText(listedApp.readableName); + if (listedApp.applicationIconRes != null) { + icon.setImageResource(listedApp.applicationIconRes); + } else { + icon.setImageDrawable(listedApp.applicationIcon); + } + installIcon.setVisibility(listedApp.isInstalled ? View.GONE : View.VISIBLE); + } + } + + public static class ApiAppsLiveData extends AsyncTaskLiveData> { + private final ApiDataAccessObject apiDao; + private final PackageManager packageManager; + + ApiAppsLiveData(Context context) { + super(context, null); + + packageManager = getContext().getPackageManager(); + apiDao = new ApiDataAccessObject(context); + } + + @Override + protected List asyncLoadData() { + ArrayList result = new ArrayList<>(); + + loadRegisteredApps(result); + addPlaceholderApps(result); + + Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName)); + return result; + } + + private void loadRegisteredApps(ArrayList result) { + List registeredApiApps = apiDao.getAllApiApps(); + + for (ApiApp apiApp : registeredApiApps) { + ListedApp listedApp; + try { + ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0); + CharSequence applicationLabel = packageManager.getApplicationLabel(ai); + Drawable applicationIcon = packageManager.getApplicationIcon(ai); + + listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null); + } catch (PackageManager.NameNotFoundException e) { + listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null); + } + result.add(listedApp); + } + } + + private void addPlaceholderApps(ArrayList result) { + for (ListedApp placeholderApp : PLACERHOLDER_APPS) { + if (!containsByPackageName(result, placeholderApp.packageName)) { + try { + packageManager.getApplicationInfo(placeholderApp.packageName, 0); + result.add(placeholderApp.withIsInstalled()); + } catch (PackageManager.NameNotFoundException e) { + result.add(placeholderApp); + } + } + } + } + + private boolean containsByPackageName(ArrayList result, String packageName) { + for (ListedApp app : result) { + if (packageName.equals(app.packageName)) { + return true; + } + } + return false; + } + } + + public static class ListedApp { + final String packageName; + final boolean isInstalled; + final boolean isRegistered; + final String readableName; + final Drawable applicationIcon; + final Integer applicationIconRes; + + ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName, + Drawable applicationIcon, Integer applicationIconRes) { + this.packageName = packageName; + this.isInstalled = isInstalled; + this.isRegistered = isRegistered; + this.readableName = readableName.toString(); + this.applicationIcon = applicationIcon; + this.applicationIconRes = applicationIconRes; + } + + public ListedApp withIsInstalled() { + return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes); + } + } + + private static final ListedApp[] PLACERHOLDER_APPS = { + new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9), + new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store), + new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null, + R.drawable.apps_conversations) }; - private static final int INDEX_ID = 0; - private static final int INDEX_PACKAGE_NAME = 1; - private static final int INDEX_NAME = 2; - private static final int INDEX_INSTALLED = 3; - private static final int INDEX_REGISTERED = 4; - private static final int INDEX_ICON_RES_ID = 5; - - public Loader onCreateLoader(int id, Bundle args) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - // First, pick the base URI to use depending on whether we are - // currently filtering. - Uri baseUri = ApiApps.CONTENT_URI; - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null, - ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC"); - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.swapCursor(data); - - // The list should now be shown. - setListShown(true); - } - - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - mAdapter.swapCursor(null); - } - - /** - * Besides the queried cursor with all registered apps, this loader also returns non-installed - * proposed apps using a MatrixCursor. - */ - private static class AppsLoader extends CursorLoader { - - public AppsLoader(Context context) { - super(context); - } - - public AppsLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - super(context, uri, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Cursor loadInBackground() { - // Load registered apps from content provider - Cursor data = super.loadInBackground(); - - MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{ - ApiApps._ID, - ApiApps.PACKAGE_NAME, - TEMP_COLUMN_NAME, - TEMP_COLUMN_INSTALLED, - TEMP_COLUMN_REGISTERED, - TEMP_COLUMN_ICON_RES_ID - }); - // NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner! - // Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png - availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9}); - availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store}); - availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations}); - - MatrixCursor mergedCursor = new MatrixCursor(new String[]{ - ApiApps._ID, - ApiApps.PACKAGE_NAME, - TEMP_COLUMN_NAME, - TEMP_COLUMN_INSTALLED, - TEMP_COLUMN_REGISTERED, - TEMP_COLUMN_ICON_RES_ID - }); - - CursorJoiner joiner = new CursorJoiner( - availableAppsCursor, - new String[]{ApiApps.PACKAGE_NAME}, - data, - new String[]{ApiApps.PACKAGE_NAME}); - for (CursorJoiner.Result joinerResult : joiner) { - switch (joinerResult) { - case LEFT: { - // handle case where a row in availableAppsCursor is unique - String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME); - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - availableAppsCursor.getString(INDEX_NAME), - isInstalled(packageName), - 0, - availableAppsCursor.getInt(INDEX_ICON_RES_ID) - }); - break; - } - case RIGHT: { - // handle case where a row in data is unique - String packageName = data.getString(INDEX_PACKAGE_NAME); - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - null, - isInstalled(packageName), - 1, // registered! - R.mipmap.ic_launcher // icon is retrieved later - }); - break; - } - case BOTH: { - // handle case where a row with the same key is in both cursors - String packageName = data.getString(INDEX_PACKAGE_NAME); - - String name; - if (isInstalled(packageName) == 1) { - name = data.getString(INDEX_NAME); - } else { - // if not installed take name from available apps list - name = availableAppsCursor.getString(INDEX_NAME); - } - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - name, - isInstalled(packageName), - 1, // registered! - R.mipmap.ic_launcher // icon is retrieved later - }); - break; - } - } - } - - return mergedCursor; - } - - private int isInstalled(String packageName) { - try { - getContext().getPackageManager().getApplicationInfo(packageName, 0); - return 1; - } catch (final PackageManager.NameNotFoundException e) { - return 0; - } - } - } - - private class AppsAdapter extends CursorAdapter { - - private LayoutInflater mInflater; - private PackageManager mPM; - - public AppsAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - - mInflater = LayoutInflater.from(context); - mPM = context.getApplicationContext().getPackageManager(); - } - - /** - * Similar to CursorAdapter.getItemId(). - * Required to build Uris for api apps, which are not based on row ids - */ - public String getItemPackageName(int position) { - if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) { - return mCursor.getString(INDEX_PACKAGE_NAME); - } else { - return null; - } - } - - public boolean getItemIsInstalled(int position) { - return mDataValid && mCursor != null - && mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1); - } - - public boolean getItemIsRegistered(int position) { - return mDataValid && mCursor != null - && mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView text = view.findViewById(R.id.api_apps_adapter_item_name); - ImageView icon = view.findViewById(R.id.api_apps_adapter_item_icon); - ImageView installIcon = view.findViewById(R.id.api_apps_adapter_install_icon); - - String packageName = cursor.getString(INDEX_PACKAGE_NAME); - Timber.d("packageName: " + packageName); - int installed = cursor.getInt(INDEX_INSTALLED); - String name = cursor.getString(INDEX_NAME); - int iconResName = cursor.getInt(INDEX_ICON_RES_ID); - - // get application name and icon - try { - ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0); - - text.setText(mPM.getApplicationLabel(ai)); - icon.setImageDrawable(mPM.getApplicationIcon(ai)); - } catch (final PackageManager.NameNotFoundException e) { - // fallback - if (name == null) { - text.setText(packageName); - } else { - text.setText(name); - try { - icon.setImageDrawable(getResources().getDrawable(iconResName)); - } catch (Resources.NotFoundException e1) { - // silently fail - } - } - } - - if (installed == 1) { - installIcon.setVisibility(View.GONE); - } else { - installIcon.setVisibility(View.VISIBLE); - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.api_apps_adapter_list_item, null); - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java index e753654ab..66b4f2965 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java @@ -26,8 +26,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.remote.AppSettings; import timber.log.Timber; @@ -39,7 +39,7 @@ class RemoteRegisterPresenter { private RemoteRegisterView view; private Intent resultData; - private AppSettings appSettings; + private ApiApp apiApp; RemoteRegisterPresenter(Context context) { @@ -54,7 +54,7 @@ class RemoteRegisterPresenter { } void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) { - this.appSettings = new AppSettings(packageName, packageSignature); + this.apiApp = ApiApp.create(packageName, packageSignature); this.resultData = resultData; try { @@ -76,7 +76,7 @@ class RemoteRegisterPresenter { } void onClickAllow() { - apiDao.insertApiApp(appSettings); + apiDao.insertApiApp(apiApp); view.finishWithResult(resultData); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 058b4ffac..91440e018 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -34,6 +34,7 @@ import timber.log.Timber; public class SelectSignKeyIdActivity extends BaseActivity { + public static final String EXTRA_PACKAGE_NAME = "package_name"; public static final String EXTRA_USER_ID = OpenPgpApi.EXTRA_USER_ID; public static final String EXTRA_DATA = "data"; @@ -68,19 +69,18 @@ public class SelectSignKeyIdActivity extends BaseActivity { }); Intent intent = getIntent(); - Uri appUri = intent.getData(); + String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID); mData = intent.getParcelableExtra(EXTRA_DATA); - if (appUri == null) { + if (packageName == null) { Timber.e("Intent data missing. Should be Uri of app!"); finish(); } else { - Timber.d("uri: " + appUri); - startListFragments(savedInstanceState, appUri, mData, mPreferredUserId); + startListFragments(savedInstanceState, packageName, mData, mPreferredUserId); } } - private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data, String preferredUserId) { + private void startListFragments(Bundle savedInstanceState, String packageName, Intent data, String preferredUserId) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -90,7 +90,7 @@ public class SelectSignKeyIdActivity extends BaseActivity { // Create an instance of the fragments SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment - .newInstance(dataUri, data, preferredUserId); + .newInstance(packageName, data, preferredUserId); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 7ddbdd82e..ff5940a05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -33,35 +33,33 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; -import timber.log.Timber; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; public class SelectSignKeyIdListFragment extends RecyclerFragment implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks { - private static final String ARG_DATA_URI = "uri"; + private static final String ARG_PACKAGE_NAME = "package_name"; private static final String ARG_PREF_UID = "pref_uid"; public static final String ARG_DATA = "data"; - private Uri mDataUri; private Intent mResult; private String mPrefUid; private ApiDataAccessObject mApiDao; + private String mPackageName; /** * Creates new instance of this fragment */ - public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data, String preferredUserId) { + public static SelectSignKeyIdListFragment newInstance(String packageName, Intent data, String preferredUserId) { SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); + args.putString(ARG_PACKAGE_NAME, packageName); args.putParcelable(ARG_DATA, data); args.putString(ARG_PREF_UID, preferredUserId); @@ -85,7 +83,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment Date: Mon, 18 Jun 2018 11:40:39 +0200 Subject: [PATCH 011/124] extract UpdatedKeys access from KeychainProvider into KeyMetadataDao --- .../keychain/keysync/KeyserverSyncWorker.java | 8 +- .../keychain/model/CustomColumnAdapters.java | 28 +++++ .../keychain/model/KeyMetadata.java | 24 ++++ .../keychain/operations/CertifyOperation.java | 12 +- .../keychain/operations/EditKeyOperation.java | 8 +- .../keychain/operations/ImportOperation.java | 12 +- .../provider/ApiDataAccessObject.java | 1 - .../provider/DatabaseNotifyManager.java | 2 +- .../keychain/provider/KeyMetadataDao.java | 69 +++++++++++ .../keychain/provider/KeyRepository.java | 28 ----- .../provider/KeyWritableRepository.java | 40 +----- .../keychain/provider/KeychainContract.java | 10 -- .../keychain/provider/KeychainDatabase.java | 18 ++- .../keychain/provider/KeychainProvider.java | 66 ---------- .../provider/LastUpdateInteractor.java | 105 ---------------- .../ui/SettingsKeyserverFragment.java | 8 +- .../keychain/ui/keyview/ViewKeyFragment.java | 6 +- .../ui/keyview/loader/KeyserverStatusDao.java | 114 ------------------ .../ui/keyview/loader/ViewKeyLiveData.java | 13 +- .../presenter/KeyserverStatusPresenter.java | 10 +- .../keychain/KeyMetadata.sq | 28 +++++ .../keychain/KeyRingsPublic.sq | 4 + .../org/sufficientlysecure/keychain/Keys.sq | 21 ++++ .../keychain/provider/InteropTest.java | 24 ++-- 24 files changed, 245 insertions(+), 414 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyMetadata.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyMetadata.sq create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq 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 4429c254a..bbcac9f08 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java @@ -26,8 +26,8 @@ import org.sufficientlysecure.keychain.operations.ImportOperation; 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.provider.LastUpdateInteractor; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; @@ -49,14 +49,14 @@ public class KeyserverSyncWorker extends Worker { Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10); private AtomicBoolean cancellationSignal = new AtomicBoolean(false); - private LastUpdateInteractor lastUpdateInteractor; + private KeyMetadataDao keyMetadataDao; private KeyWritableRepository keyWritableRepository; private Preferences preferences; @NonNull @Override public WorkerResult doWork() { - lastUpdateInteractor = LastUpdateInteractor.create(getApplicationContext()); + keyMetadataDao = KeyMetadataDao.create(getApplicationContext()); keyWritableRepository = KeyWritableRepository.create(getApplicationContext()); preferences = Preferences.getPreferences(getApplicationContext()); @@ -73,7 +73,7 @@ public class KeyserverSyncWorker extends Worker { Progressable notificationProgressable) { long staleKeyThreshold = System.currentTimeMillis() - (isForceUpdate ? 0 : KEY_STALE_THRESHOLD_MILLIS); List staleKeyFingerprints = - lastUpdateInteractor.getFingerprintsForKeysOlderThan(staleKeyThreshold, TimeUnit.MILLISECONDS); + keyMetadataDao.getFingerprintsForKeysOlderThan(staleKeyThreshold, TimeUnit.MILLISECONDS); List staleKeyParcelableKeyRings = fingerprintListToParcelableKeyRings(staleKeyFingerprints); if (isStopped()) { // if we've already been cancelled diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java new file mode 100644 index 000000000..2bc69aa1a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java @@ -0,0 +1,28 @@ +package org.sufficientlysecure.keychain.model; + + +import java.util.Date; + +import android.support.annotation.NonNull; + +import com.squareup.sqldelight.ColumnAdapter; + + +public final class CustomColumnAdapters { + + private CustomColumnAdapters() { } + + static final ColumnAdapter DATE_ADAPTER = new ColumnAdapter() { + @NonNull + @Override + public Date decode(Long databaseValue) { + // Both SQLite and OpenPGP prefer a second granularity for timestamps - so we'll translate here + return new Date(databaseValue * 1000); + } + + @Override + public Long encode(@NonNull Date value) { + return value.getTime() / 1000; + } + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyMetadata.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyMetadata.java new file mode 100644 index 000000000..c1df957b8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyMetadata.java @@ -0,0 +1,24 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.KeyMetadataModel; + + +@AutoValue +public abstract class KeyMetadata implements KeyMetadataModel { + public static final Factory FACTORY = new Factory<>( + AutoValue_KeyMetadata::new, CustomColumnAdapters.DATE_ADAPTER); + + public boolean hasBeenUpdated() { + return last_updated() != null; + } + + public boolean isPublished() { + if (last_updated() == null) { + throw new IllegalStateException("Cannot get publication state if key has never been updated!"); + } + Boolean seenOnKeyservers = seen_on_keyservers(); + return seenOnKeyservers != null && seenOnKeyservers; + } +} 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 e2372fdd8..92bea7d39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -41,7 +41,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.LastUpdateInteractor; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; @@ -62,13 +62,13 @@ import org.sufficientlysecure.keychain.util.Passphrase; * @see CertifyActionsParcel */ public class CertifyOperation extends BaseReadWriteOperation { - private final LastUpdateInteractor lastUpdateInteractor; + private final KeyMetadataDao keyMetadataDao; - public CertifyOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable, AtomicBoolean + public CertifyOperation(Context context, KeyWritableRepository keyWritableRepository, Progressable progressable, AtomicBoolean cancelled) { - super(context, databaseInteractor, progressable, cancelled); + super(context, keyWritableRepository, progressable, cancelled); - this.lastUpdateInteractor = LastUpdateInteractor.create(context); + this.keyMetadataDao = KeyMetadataDao.create(context); } @NonNull @@ -234,7 +234,7 @@ public class CertifyOperation extends BaseReadWriteOperation { - private final LastUpdateInteractor lastUpdateInteractor; + private final KeyMetadataDao keyMetadataDao; public EditKeyOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable, AtomicBoolean cancelled) { super(context, databaseInteractor, progressable, cancelled); - this.lastUpdateInteractor = LastUpdateInteractor.create(context); + this.keyMetadataDao = KeyMetadataDao.create(context); } /** @@ -172,7 +172,7 @@ public class EditKeyOperation extends BaseReadWriteOperation log.add(saveResult, 1); if (isNewKey) { - lastUpdateInteractor.renewKeyLastUpdatedTime(ring.getMasterKeyId(), saveParcel.isShouldUpload()); + keyMetadataDao.renewKeyLastUpdatedTime(ring.getMasterKeyId(), saveParcel.isShouldUpload()); } // If the save operation didn't succeed, exit here 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 1bee1c825..4ec08b046 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -55,7 +55,7 @@ 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.LastUpdateInteractor; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -90,7 +90,7 @@ public class ImportOperation extends BaseReadWriteOperation public static final String CACHE_FILE_NAME = "key_import.pcl"; - private final LastUpdateInteractor lastUpdateInteractor; + private final KeyMetadataDao keyMetadataDao; private FacebookKeyserverClient facebookServer; private KeybaseKeyserverClient keybaseServer; @@ -98,14 +98,14 @@ public class ImportOperation extends BaseReadWriteOperation public ImportOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable) { super(context, databaseInteractor, progressable); - this.lastUpdateInteractor = LastUpdateInteractor.create(context); + this.keyMetadataDao = KeyMetadataDao.create(context); } public ImportOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable, AtomicBoolean cancelled) { super(context, databaseInteractor, progressable, cancelled); - this.lastUpdateInteractor = LastUpdateInteractor.create(context); + this.keyMetadataDao = KeyMetadataDao.create(context); } // Overloaded functions for using progressable supplied in constructor during import @@ -200,7 +200,7 @@ public class ImportOperation extends BaseReadWriteOperation byte[] fingerprintHex = entry.getExpectedFingerprint(); if (fingerprintHex != null) { - lastUpdateInteractor.renewKeyLastUpdatedTime( + keyMetadataDao.renewKeyLastUpdatedTime( KeyFormattingUtils.getKeyIdFromFingerprint(fingerprintHex), false); } continue; @@ -250,7 +250,7 @@ public class ImportOperation extends BaseReadWriteOperation } if (!skipSave) { - lastUpdateInteractor.renewKeyLastUpdatedTime(key.getMasterKeyId(), keyWasDownloaded); + keyMetadataDao.renewKeyLastUpdatedTime(key.getMasterKeyId(), keyWasDownloaded); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java index 16082fb56..936519cf8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -37,7 +37,6 @@ import org.sufficientlysecure.keychain.model.ApiApp; public class ApiDataAccessObject { - private final SupportSQLiteDatabase db; public ApiDataAccessObject(Context context) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java index b283dd576..1bfb0fe65 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java @@ -35,7 +35,7 @@ public class DatabaseNotifyManager { contentResolver.notifyChange(uri, null); } - public void notifyKeyserverStatusChange(long masterKeyId) { + public void notifyKeyMetadataChange(long masterKeyId) { Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); contentResolver.notifyChange(uri, null); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java new file mode 100644 index 000000000..f98258c8b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java @@ -0,0 +1,69 @@ +package org.sufficientlysecure.keychain.provider; + + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.content.Context; +import android.database.Cursor; + +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.KeyMetadataModel.ReplaceKeyMetadata; +import org.sufficientlysecure.keychain.model.KeyMetadata; + + +public class KeyMetadataDao { + private final SupportSQLiteDatabase db; + private DatabaseNotifyManager databaseNotifyManager; + + + public static KeyMetadataDao create(Context context) { + SupportSQLiteDatabase supportSQLiteDatabase = new KeychainDatabase(context).getWritableDatabase(); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); + + return new KeyMetadataDao(supportSQLiteDatabase, databaseNotifyManager); + } + + private KeyMetadataDao(SupportSQLiteDatabase supportSQLiteDatabase, DatabaseNotifyManager databaseNotifyManager) { + this.db = supportSQLiteDatabase; + this.databaseNotifyManager = databaseNotifyManager; + } + + public KeyMetadata getKeyMetadata(long masterKeyId) { + SqlDelightQuery query = KeyMetadata.FACTORY.selectByMasterKeyId(masterKeyId); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToFirst()) { + return KeyMetadata.FACTORY.selectByMasterKeyIdMapper().map(cursor); + } + } + return null; + } + + public void resetAllLastUpdatedTimes() { + new KeyMetadata.DeleteAllLastUpdatedTimes(db).execute(); + } + + public void renewKeyLastUpdatedTime(long masterKeyId, boolean seenOnKeyservers) { + ReplaceKeyMetadata replaceStatement = new ReplaceKeyMetadata(db, KeyMetadata.FACTORY); + replaceStatement.bind(masterKeyId, new Date(), seenOnKeyservers); + replaceStatement.executeInsert(); + + databaseNotifyManager.notifyKeyMetadataChange(masterKeyId); + } + + public List getFingerprintsForKeysOlderThan(long olderThan, TimeUnit timeUnit) { + SqlDelightQuery query = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThan(new Date(timeUnit.toMillis(olderThan))); + + List fingerprintList = new ArrayList<>(); + try (Cursor cursor = db.query(query)) { + while (cursor.moveToNext()) { + byte[] fingerprint = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThanMapper().map(cursor); + fingerprintList.add(fingerprint); + } + } + return fingerprintList; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index f0afe66ee..5b5f6e82a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -27,7 +27,6 @@ import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; -import android.support.annotation.Nullable; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -39,9 +38,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import timber.log.Timber; @@ -284,31 +281,6 @@ public class KeyRepository { return contentResolver; } - @Nullable - Long getLastUpdateTime(long masterKeyId) { - Cursor lastUpdatedCursor = contentResolver.query( - UpdatedKeys.CONTENT_URI, - new String[] { UpdatedKeys.LAST_UPDATED }, - Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = ?", - new String[] { "" + masterKeyId }, - null - ); - if (lastUpdatedCursor == null) { - return null; - } - - Long lastUpdateTime; - try { - if (!lastUpdatedCursor.moveToNext()) { - return null; - } - lastUpdateTime = lastUpdatedCursor.getLong(0); - } finally { - lastUpdatedCursor.close(); - } - return lastUpdateTime; - } - public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException { byte[] data = (byte[]) getGenericDataOrNull(KeyRingData.buildPublicKeyRingUri(masterKeyId), KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 4602112b1..d2e1af3e4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -61,7 +61,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -84,36 +83,30 @@ public class KeyWritableRepository extends KeyRepository { private static final int MAX_CACHED_KEY_SIZE = 1024 * 50; private final Context context; - private final LastUpdateInteractor lastUpdateInteractor; private final DatabaseNotifyManager databaseNotifyManager; public static KeyWritableRepository create(Context context) { LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - LastUpdateInteractor lastUpdateInteractor = LastUpdateInteractor.create(context); - return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, - lastUpdateInteractor); + return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager); } @VisibleForTesting KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, - DatabaseNotifyManager databaseNotifyManager, - LastUpdateInteractor lastUpdateInteractor) { - this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, lastUpdateInteractor, - new OperationLog(), 0); + DatabaseNotifyManager databaseNotifyManager) { + this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0); } private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, - LastUpdateInteractor lastUpdateInteractor, OperationLog log, int indent) { + OperationLog log, int indent) { super(context.getContentResolver(), localPublicKeyStorage, localSecretKeyStorage, log, indent); this.context = context; this.databaseNotifyManager = databaseNotifyManager; - this.lastUpdateInteractor = lastUpdateInteractor; } private LongSparseArray getTrustedMasterKeys() { @@ -532,11 +525,6 @@ public class KeyWritableRepository extends KeyRepository { mIndent -= 1; } - ContentProviderOperation lastUpdateReinsertOp = getLastUpdatedReinsertOperationByMasterKeyId(masterKeyId); - if (lastUpdateReinsertOp != null) { - operations.add(lastUpdateReinsertOp); - } - try { // delete old version of this keyRing (from database only!), which also deletes all keys and userIds on cascade int deleted = contentResolver.delete( @@ -567,26 +555,6 @@ public class KeyWritableRepository extends KeyRepository { } - private ContentProviderOperation getLastUpdatedReinsertOperationByMasterKeyId(long masterKeyId) { - Long lastUpdateTime = getLastUpdateTime(masterKeyId); - if (lastUpdateTime == null) { - return null; - } - - Boolean seenOnKeyservers = lastUpdateInteractor.getSeenOnKeyservers(masterKeyId); - - ContentValues lastUpdatedEntry = new ContentValues(2); - lastUpdatedEntry.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId); - lastUpdatedEntry.put(UpdatedKeys.LAST_UPDATED, lastUpdateTime); - if (seenOnKeyservers != null){ - lastUpdatedEntry.put(UpdatedKeys.SEEN_ON_KEYSERVERS, seenOnKeyservers); - } - return ContentProviderOperation - .newInsert(UpdatedKeys.CONTENT_URI) - .withValues(lastUpdatedEntry) - .build(); - } - private void writePublicKeyRing(CanonicalizedPublicKeyRing keyRing, long masterKeyId, ArrayList operations) throws IOException { byte[] encodedKey = keyRing.getEncoded(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index a47583170..a44da1370 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -263,16 +263,6 @@ public class KeychainContract { } - public static class UpdatedKeys implements UpdatedKeysColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_UPDATED_KEYS).build(); - - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.updated_keys"; - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.updated_keys"; - } - public static class KeySignatures implements KeySignaturesColumns, BaseColumns { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_SIGNATURES).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index e35b6a85d..81861e9e8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -34,6 +34,9 @@ import android.database.sqlite.SQLiteException; import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.KeyMetadataModel; +import org.sufficientlysecure.keychain.KeyRingsPublicModel; +import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; @@ -57,7 +60,7 @@ import timber.log.Timber; */ public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 26; + private static final int DATABASE_VERSION = 27; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; private Context context; @@ -226,15 +229,16 @@ public class KeychainDatabase { private void onCreate(SupportSQLiteDatabase db) { Timber.w("Creating database..."); - db.execSQL(CREATE_KEYRINGS_PUBLIC); + db.execSQL(KeyRingsPublicModel.CREATE_TABLE); db.execSQL(CREATE_KEYS); db.execSQL(CREATE_USER_PACKETS); db.execSQL(CREATE_CERTS); - db.execSQL(CREATE_UPDATE_KEYS); + db.execSQL(KeyMetadataModel.CREATE_TABLE); db.execSQL(CREATE_KEY_SIGNATURES); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(CREATE_API_AUTOCRYPT_PEERS); + db.execSQL(ApiApp.CREATE_TABLE); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " @@ -459,6 +463,9 @@ public class KeychainDatabase { } } + case 26: { + migrateUpdatedKeysToKeyMetadataTable(db); + } } } @@ -476,6 +483,11 @@ public class KeychainDatabase { // db.execSQL("DROP TABLE keyrings_secret"); } + private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;"); + db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;"); + } + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { // Downgrade is ok for the debug version, makes it easier to work with branches if (Constants.DEBUG) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 1f64a9a49..805dd8036 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -45,7 +45,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; @@ -75,9 +74,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RINGS_FIND_BY_USER_ID = 402; private static final int KEY_RINGS_FILTER_BY_SIGNER = 403; - private static final int UPDATED_KEYS = 500; - private static final int UPDATED_KEYS_SPECIFIC = 501; - private static final int AUTOCRYPT_PEERS_BY_MASTER_KEY_ID = 601; private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME = 602; private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID = 603; @@ -192,14 +188,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" + KeychainContract.PATH_BY_PACKAGE_NAME + "/*/*", AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID); - - /* - * to access table containing last updated dates of keys - */ - matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS, UPDATED_KEYS); - matcher.addURI(authority, KeychainContract.BASE_UPDATED_KEYS + "/*", UPDATED_KEYS_SPECIFIC); - - matcher.addURI(authority, KeychainContract.BASE_KEY_SIGNATURES, KEY_SIGNATURES); @@ -239,12 +227,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case KEY_RING_USER_IDS: return UserPackets.CONTENT_TYPE; - case UPDATED_KEYS: - return UpdatedKeys.CONTENT_TYPE; - - case UPDATED_KEYS_SPECIFIC: - return UpdatedKeys.CONTENT_ITEM_TYPE; - case KEY_SIGNATURES: return KeySignatures.CONTENT_TYPE; @@ -727,30 +709,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case UPDATED_KEYS: - case UPDATED_KEYS_SPECIFIC: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(UpdatedKeys.MASTER_KEY_ID, - Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " AS " + UpdatedKeys.MASTER_KEY_ID); - projectionMap.put(UpdatedKeys.LAST_UPDATED, - Tables.UPDATED_KEYS + "." + UpdatedKeys.LAST_UPDATED + " AS " + UpdatedKeys.LAST_UPDATED); - projectionMap.put(UpdatedKeys.SEEN_ON_KEYSERVERS, - Tables.UPDATED_KEYS + "." + UpdatedKeys.SEEN_ON_KEYSERVERS + " AS " + UpdatedKeys.SEEN_ON_KEYSERVERS); - projectionMap.put(UpdatedKeys.FINGERPRINT, - Tables.KEYS + "." + Keys.FINGERPRINT + " AS " + UpdatedKeys.FINGERPRINT); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.UPDATED_KEYS + - " LEFT JOIN " + Tables.KEYS + - " ON (" + Tables.KEYS + "." + Keys.KEY_ID + " = " + Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + ")" - ); - - if (match == UPDATED_KEYS_SPECIFIC) { - qb.appendWhere(Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - } - break; - } default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -835,17 +793,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe keyId = values.getAsLong(Certs.MASTER_KEY_ID); break; } - case UPDATED_KEYS: { - keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID); - try { - db.insert(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values); - } catch (SQLiteConstraintException e) { - db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_IGNORE, values, - UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(keyId) }); - } - rowUri = UpdatedKeys.CONTENT_URI; - break; - } case KEY_SIGNATURES: { db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values); rowUri = KeySignatures.CONTENT_URI; @@ -953,19 +900,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs); break; } - case UPDATED_KEYS: { - if (values.size() != 2 || - !values.containsKey(UpdatedKeys.SEEN_ON_KEYSERVERS) || - !values.containsKey(UpdatedKeys.LAST_UPDATED) || - values.get(UpdatedKeys.LAST_UPDATED) != null || - values.get(UpdatedKeys.SEEN_ON_KEYSERVERS) != null || - selection != null || selectionArgs != null) { - throw new UnsupportedOperationException("can only reset all keys"); - } - - db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values, null, null); - break; - } case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { String packageName = uri.getPathSegments().get(2); String identifier = uri.getLastPathSegment(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java deleted file mode 100644 index 268df323d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.sufficientlysecure.keychain.provider; - - -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.support.annotation.Nullable; - -import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; - - -public class LastUpdateInteractor { - private final ContentResolver contentResolver; - private final DatabaseNotifyManager databaseNotifyManager; - - - public static LastUpdateInteractor create(Context context) { - return new LastUpdateInteractor(context.getContentResolver(), DatabaseNotifyManager.create(context)); - } - - private LastUpdateInteractor(ContentResolver contentResolver, DatabaseNotifyManager databaseNotifyManager) { - this.contentResolver = contentResolver; - this.databaseNotifyManager = databaseNotifyManager; - } - - @Nullable - public Boolean getSeenOnKeyservers(long masterKeyId) { - Cursor cursor = contentResolver.query( - UpdatedKeys.CONTENT_URI, - new String[] { UpdatedKeys.SEEN_ON_KEYSERVERS }, - Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = ?", - new String[] { "" + masterKeyId }, - null - ); - if (cursor == null) { - return null; - } - - Boolean seenOnKeyservers; - try { - if (!cursor.moveToNext()) { - return null; - } - seenOnKeyservers = cursor.isNull(0) ? null : cursor.getInt(0) != 0; - } finally { - cursor.close(); - } - return seenOnKeyservers; - } - - public void resetAllLastUpdatedTimes() { - ContentValues values = new ContentValues(); - values.putNull(UpdatedKeys.LAST_UPDATED); - values.putNull(UpdatedKeys.SEEN_ON_KEYSERVERS); - contentResolver.update(UpdatedKeys.CONTENT_URI, values, null, null); - } - - public Uri renewKeyLastUpdatedTime(long masterKeyId, boolean seenOnKeyservers) { - boolean isFirstKeyserverStatusCheck = getSeenOnKeyservers(masterKeyId) == null; - - ContentValues values = new ContentValues(); - values.put(UpdatedKeys.MASTER_KEY_ID, masterKeyId); - values.put(UpdatedKeys.LAST_UPDATED, GregorianCalendar.getInstance().getTimeInMillis() / 1000); - if (seenOnKeyservers || isFirstKeyserverStatusCheck) { - values.put(UpdatedKeys.SEEN_ON_KEYSERVERS, seenOnKeyservers); - } - - // this will actually update/replace, doing the right thing™ for seenOnKeyservers value - // see `KeychainProvider.insert()` - Uri insert = contentResolver.insert(UpdatedKeys.CONTENT_URI, values); - databaseNotifyManager.notifyKeyserverStatusChange(masterKeyId); - return insert; - } - - public List getFingerprintsForKeysOlderThan(long olderThan, TimeUnit timeUnit) { - Cursor outdatedKeysCursor = contentResolver.query( - KeychainContract.UpdatedKeys.CONTENT_URI, - new String[] { KeychainContract.UpdatedKeys.FINGERPRINT, }, - KeychainContract.UpdatedKeys.LAST_UPDATED + " < ?", - new String[] { Long.toString(timeUnit.toSeconds(olderThan)) }, - null - ); - - List fingerprintList = new ArrayList<>(); - if (outdatedKeysCursor == null) { - return fingerprintList; - } - - while (outdatedKeysCursor.moveToNext()) { - byte[] fingerprint = outdatedKeysCursor.getBlob(0); - fingerprintList.add(fingerprint); - } - outdatedKeysCursor.close(); - - return fingerprintList; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java index 5b20987fc..d60734651 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -43,7 +43,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; -import org.sufficientlysecure.keychain.provider.LastUpdateInteractor; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -63,7 +63,7 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC private List mKeyservers; private KeyserverListAdapter mAdapter; - private LastUpdateInteractor lastUpdateInteractor; + private KeyMetadataDao keyMetadataDao; public static SettingsKeyserverFragment newInstance(ArrayList keyservers) { Bundle args = new Bundle(); @@ -78,7 +78,7 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - lastUpdateInteractor = LastUpdateInteractor.create(getContext()); + keyMetadataDao = KeyMetadataDao.create(getContext()); return inflater.inflate(R.layout.settings_keyserver_fragment, null); } @@ -230,7 +230,7 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC Preferences.getPreferences(getActivity()).setKeyServers(mKeyserversMutable); mKeyservers = Collections.unmodifiableList(new ArrayList<>(mKeyserversMutable)); - lastUpdateInteractor.resetAllLastUpdatedTimes(); + keyMetadataDao.resetAllLastUpdatedTimes(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index b1e675583..07e59d289 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -39,10 +39,10 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.model.KeyMetadata; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusDao.KeyserverStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter; @@ -108,7 +108,7 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O private LiveData> identityInfo; private LiveData subkeyStatus; private LiveData systemContactInfo; - private LiveData keyserverStatus; + private LiveData keyserverStatus; LiveData> getIdentityInfo(IdentitiesPresenter identitiesPresenter) { if (identityInfo == null) { @@ -131,7 +131,7 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O return systemContactInfo; } - LiveData getKeyserverStatus(KeyserverStatusPresenter keyserverStatusPresenter) { + LiveData getKeyserverStatus(KeyserverStatusPresenter keyserverStatusPresenter) { if (keyserverStatus == null) { keyserverStatus = keyserverStatusPresenter.getLiveDataInstance(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java deleted file mode 100644 index 7fdc0c6d5..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.keyview.loader; - - -import java.util.Date; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; - -import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import timber.log.Timber; - - -public class KeyserverStatusDao { - public static final String[] PROJECTION = new String[] { - UpdatedKeys.LAST_UPDATED, - UpdatedKeys.SEEN_ON_KEYSERVERS - }; - private static final int INDEX_LAST_UPDATED = 0; - private static final int INDEX_SEEN_ON_KEYSERVERS = 1; - - - private final ContentResolver contentResolver; - - public static KeyserverStatusDao getInstance(Context context) { - ContentResolver contentResolver = context.getContentResolver(); - return new KeyserverStatusDao(contentResolver); - } - - private KeyserverStatusDao(ContentResolver contentResolver) { - this.contentResolver = contentResolver; - } - - public KeyserverStatus getKeyserverStatus(long masterKeyId) { - Cursor cursor = contentResolver.query(UpdatedKeys.CONTENT_URI, PROJECTION, - Tables.UPDATED_KEYS + "." + UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(masterKeyId) }, null); - if (cursor == null) { - Timber.e("Error loading key items!"); - return null; - } - - try { - if (cursor.moveToFirst()) { - if (cursor.isNull(INDEX_SEEN_ON_KEYSERVERS) || cursor.isNull(INDEX_LAST_UPDATED)) { - return new KeyserverStatus(masterKeyId); - } - - boolean isPublished = cursor.getInt(INDEX_SEEN_ON_KEYSERVERS) != 0; - Date lastUpdated = new Date(cursor.getLong(INDEX_LAST_UPDATED) * 1000); - - return new KeyserverStatus(masterKeyId, isPublished, lastUpdated); - } - - return new KeyserverStatus(masterKeyId); - } finally { - cursor.close(); - } - } - - public static class KeyserverStatus { - private final long masterKeyId; - private final boolean isPublished; - private final Date lastUpdated; - - KeyserverStatus(long masterKeyId, boolean isPublished, Date lastUpdated) { - this.masterKeyId = masterKeyId; - this.isPublished = isPublished; - this.lastUpdated = lastUpdated; - } - - KeyserverStatus(long masterKeyId) { - this.masterKeyId = masterKeyId; - this.isPublished = false; - this.lastUpdated = null; - } - - long getMasterKeyId() { - return masterKeyId; - } - - public boolean hasBeenUpdated() { - return lastUpdated != null; - } - - public boolean isPublished() { - if (lastUpdated == null) { - throw new IllegalStateException("Cannot get publication state if key has never been updated!"); - } - return isPublished; - } - - public Date getLastUpdated() { - return lastUpdated; - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java index c9bb64dd9..8d09591ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java @@ -6,9 +6,10 @@ import java.util.List; import android.content.Context; +import org.sufficientlysecure.keychain.model.KeyMetadata; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusDao.KeyserverStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.SubKeyItem; import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; @@ -78,21 +79,21 @@ public class ViewKeyLiveData { } } - public static class KeyserverStatusLiveData extends AsyncTaskLiveData { - private final KeyserverStatusDao keyserverStatusDao; + public static class KeyserverStatusLiveData extends AsyncTaskLiveData { + private final KeyMetadataDao keyMetadataDao; private final long masterKeyId; public KeyserverStatusLiveData(Context context, long masterKeyId) { super(context, KeyRings.buildGenericKeyRingUri(masterKeyId)); - this.keyserverStatusDao = KeyserverStatusDao.getInstance(context); + this.keyMetadataDao = KeyMetadataDao.create(context); this.masterKeyId = masterKeyId; } @Override - public KeyserverStatus asyncLoadData() { - return keyserverStatusDao.getKeyserverStatus(masterKeyId); + public KeyMetadata asyncLoadData() { + return keyMetadataDao.getKeyMetadata(masterKeyId); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java index 76e63f15e..c0548848f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java @@ -25,11 +25,11 @@ import android.arch.lifecycle.Observer; import android.content.Context; import android.support.annotation.Nullable; -import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusDao.KeyserverStatus; +import org.sufficientlysecure.keychain.model.KeyMetadata; import org.sufficientlysecure.keychain.ui.keyview.loader.ViewKeyLiveData.KeyserverStatusLiveData; -public class KeyserverStatusPresenter implements Observer { +public class KeyserverStatusPresenter implements Observer { private final Context context; private final KeyserverStatusMvpView view; @@ -46,12 +46,12 @@ public class KeyserverStatusPresenter implements Observer { this.isSecret = isSecret; } - public LiveData getLiveDataInstance() { + public LiveData getLiveDataInstance() { return new KeyserverStatusLiveData(context, masterKeyId); } @Override - public void onChanged(@Nullable KeyserverStatus keyserverStatus) { + public void onChanged(@Nullable KeyMetadata keyserverStatus) { if (keyserverStatus == null) { view.setDisplayStatusUnknown(); return; @@ -63,7 +63,7 @@ public class KeyserverStatusPresenter implements Observer { } else { view.setDisplayStatusNotPublished(); } - view.setLastUpdated(keyserverStatus.getLastUpdated()); + view.setLastUpdated(keyserverStatus.last_updated()); } else { view.setDisplayStatusUnknown(); } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyMetadata.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyMetadata.sq new file mode 100644 index 000000000..0f41a6c2a --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyMetadata.sq @@ -0,0 +1,28 @@ +import java.lang.Boolean; +import java.util.Date; + +CREATE TABLE IF NOT EXISTS key_metadata ( + master_key_id INTEGER PRIMARY KEY, + last_updated INTEGER AS Date, + seen_on_keyservers INTEGER AS Boolean, + FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE +); + +selectByMasterKeyId: +SELECT * + FROM key_metadata + WHERE master_key_id = ?; + +deleteAllLastUpdatedTimes: +UPDATE key_metadata + SET last_updated = null, seen_on_keyservers = null; + +replaceKeyMetadata: +REPLACE INTO key_metadata + (master_key_id, last_updated, seen_on_keyservers) VALUES (?, ?, ?); + +selectFingerprintsForKeysOlderThan: +SELECT fingerprint + FROM key_metadata + LEFT JOIN keys ON (key_metadata.master_key_id = keys.master_key_id AND keys.rank = 0) + WHERE last_updated < ?; \ No newline at end of file diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq new file mode 100644 index 000000000..be8435d7b --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS keyrings_public ( + master_key_id INTEGER PRIMARY KEY, + key_ring_data BLOB +); \ No newline at end of file diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq new file mode 100644 index 000000000..69bbd4b91 --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS keys ( + master_key_id INTEGER, + rank INTEGER, + key_id INTEGER, + key_size INTEGER, + key_curve_oid TEXT, + algorithm INTEGER, + fingerprint BLOB, + can_certify INTEGER, + can_sign INTEGER, + can_encrypt INTEGER, + can_authenticate INTEGER, + is_revoked INTEGER, + has_secret INTEGER, + is_secure INTEGER, + creation INTEGER, + expiry INTEGER, + PRIMARY KEY(master_key_id, rank), + FOREIGN KEY(master_key_id) REFERENCES + keyrings_public(master_key_id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index 357960e94..14288f7bb 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -16,6 +16,17 @@ package org.sufficientlysecure.keychain.provider; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.net.URL; +import java.security.Security; +import java.util.ArrayList; + import android.net.Uri; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -47,16 +58,6 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Passphrase; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.net.URL; -import java.security.Security; -import java.util.ArrayList; - @RunWith(KeychainTestRunner.class) public class InteropTest { @@ -245,8 +246,7 @@ public class InteropTest { KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application, LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application), LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application), - DatabaseNotifyManager.create(RuntimeEnvironment.application), - LastUpdateInteractor.create(RuntimeEnvironment.application)) { + DatabaseNotifyManager.create(RuntimeEnvironment.application)) { @Override public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { From e144a402b5e0e53bb64c468a2d68f3605fd36f11 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 18 Jun 2018 23:03:09 +0200 Subject: [PATCH 012/124] extract autocrypt_peers from KeychainProvider into AutocryptPeerDao --- .../keychain/model/AutocryptPeer.java | 61 ++++ .../keychain/model/CustomColumnAdapters.java | 25 ++ .../provider/ApiDataAccessObject.java | 8 +- .../keychain/provider/AutocryptPeerDao.java | 162 +++++++++ .../AutocryptPeerDataAccessObject.java | 328 ------------------ .../provider/KeyWritableRepository.java | 16 +- .../keychain/provider/KeychainContract.java | 64 ---- .../keychain/provider/KeychainDatabase.java | 35 +- .../provider/KeychainExternalContract.java | 1 - .../keychain/provider/KeychainProvider.java | 131 +------ .../keychain/remote/AutocryptInteractor.java | 135 ++++++- .../remote/KeychainExternalProvider.java | 25 +- .../keychain/remote/OpenPgpService.java | 16 +- .../ui/dialog/RemoteDeduplicatePresenter.java | 13 +- .../ui/keyview/loader/IdentityDao.java | 60 ++-- .../presenter/IdentitiesPresenter.java | 8 +- .../keychain/AutocryptPeers.sq | 64 ++++ .../org/sufficientlysecure/keychain/Certs.sq | 13 + .../keychain/provider/InteropTest.java | 3 +- .../remote/KeychainExternalProviderTest.java | 28 +- 20 files changed, 546 insertions(+), 650 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java new file mode 100644 index 000000000..b6dcc32f8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java @@ -0,0 +1,61 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import com.squareup.sqldelight.RowMapper; +import org.sufficientlysecure.keychain.AutocryptPeersModel; + + +@AutoValue +public abstract class AutocryptPeer implements AutocryptPeersModel { + + public enum GossipOrigin { + GOSSIP_HEADER, SIGNATURE, DEDUP + } + + public static final Factory FACTORY = new Factory(AutoValue_AutocryptPeer::new, + CustomColumnAdapters.DATE_ADAPTER, CustomColumnAdapters.DATE_ADAPTER, CustomColumnAdapters.DATE_ADAPTER, + CustomColumnAdapters.GOSSIP_ORIGIN_ADAPTER); + + public static final RowMapper PEER_MAPPER = FACTORY.selectByIdentifiersMapper(); + public static final RowMapper KEY_STATUS_MAPPER = + FACTORY.selectAutocryptKeyStatusMapper(AutoValue_AutocryptPeer_AutocryptKeyStatus::new); + + @AutoValue + public static abstract class AutocryptKeyStatus implements SelectAutocryptKeyStatusModel { + public boolean hasGossipKey() { + return autocryptPeer().gossip_master_key_id() != null; + } + + public boolean isGossipKeyRevoked() { + Long revokedInt = gossip_key_is_revoked_int(); + return revokedInt != null && revokedInt != 0; + } + + public boolean isGossipKeyExpired() { + return gossip_key_is_expired_int() != 0; + } + + public boolean isGossipKeyVerified() { + return gossip_key_is_verified_int() != 0; + } + + public boolean hasKey() { + return autocryptPeer().master_key_id() != null; + } + + public boolean isKeyRevoked() { + Long revokedInt = key_is_revoked_int(); + return revokedInt != null && revokedInt != 0; + } + + public boolean isKeyExpired() { + return key_is_expired_int() != 0; + } + + public boolean isKeyVerified() { + return key_is_verified_int() != 0; + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java index 2bc69aa1a..e6b9d0ee7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java @@ -6,6 +6,8 @@ import java.util.Date; import android.support.annotation.NonNull; import com.squareup.sqldelight.ColumnAdapter; +import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; + public final class CustomColumnAdapters { @@ -25,4 +27,27 @@ public final class CustomColumnAdapters { return value.getTime() / 1000; } }; + + static final ColumnAdapter GOSSIP_ORIGIN_ADAPTER = new ColumnAdapter() { + @NonNull + @Override + public GossipOrigin decode(Long databaseValue) { + switch (databaseValue.intValue()) { + case 0: return GossipOrigin.GOSSIP_HEADER; + case 10: return GossipOrigin.SIGNATURE; + case 20: return GossipOrigin.DEDUP; + default: throw new IllegalArgumentException("Unhandled database value!"); + } + } + + @Override + public Long encode(@NonNull GossipOrigin value) { + switch (value) { + case GOSSIP_HEADER: return 0L; + case SIGNATURE: return 10L; + case DEDUP: return 20L; + default: throw new IllegalArgumentException("Unhandled database value!"); + } + } + }; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java index 936519cf8..22f1b9cc8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -54,8 +54,12 @@ public class ApiDataAccessObject { } public byte[] getApiAppCertificate(String packageName) { - Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName)); - return ApiApp.FACTORY.getCertificateMapper().map(cursor); + try (Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName))) { + if (cursor.moveToFirst()) { + return ApiApp.FACTORY.getCertificateMapper().map(cursor); + } + return null; + } } public void insertApiApp(ApiApp apiApp) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java new file mode 100644 index 000000000..bdf79f6a7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.provider; + + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.content.Context; +import android.database.Cursor; +import android.support.annotation.Nullable; + +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByIdentifier; +import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByMasterKeyId; +import org.sufficientlysecure.keychain.AutocryptPeersModel.InsertPeer; +import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateGossipKey; +import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateKey; +import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateLastSeen; +import org.sufficientlysecure.keychain.model.AutocryptPeer; +import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus; +import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; + + +public class AutocryptPeerDao { + private final SupportSQLiteDatabase db; + private final DatabaseNotifyManager databaseNotifyManager; + + public static AutocryptPeerDao getInstance(Context context) { + KeychainDatabase keychainDatabase = new KeychainDatabase(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); + + return new AutocryptPeerDao(keychainDatabase.getWritableDatabase(), databaseNotifyManager); + } + + private AutocryptPeerDao(SupportSQLiteDatabase writableDatabase, DatabaseNotifyManager databaseNotifyManager) { + this.db = writableDatabase; + this.databaseNotifyManager = databaseNotifyManager; + } + + public Long getMasterKeyIdForAutocryptPeer(String autocryptId) { + SqlDelightQuery query = AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifier(autocryptId); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToFirst()) { + return AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifierMapper().map(cursor); + } + } + return null; + } + + @Nullable + public AutocryptPeer getAutocryptPeer(String packageName, String autocryptId) { + List autocryptPeers = getAutocryptPeers(packageName, autocryptId); + if (!autocryptPeers.isEmpty()) { + return autocryptPeers.get(0); + } + return null; + } + + public List getAutocryptPeers(String packageName, String... autocryptId) { + ArrayList result = new ArrayList<>(autocryptId.length); + SqlDelightQuery query = AutocryptPeer.FACTORY.selectByIdentifiers(packageName, autocryptId); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToNext()) { + AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor); + result.add(autocryptPeer); + } + } + return result; + } + + public List getAutocryptKeyStatus(String packageName, String[] autocryptIds) { + ArrayList result = new ArrayList<>(autocryptIds.length); + SqlDelightQuery query = AutocryptPeer.FACTORY.selectAutocryptKeyStatus(packageName, autocryptIds, System.currentTimeMillis()); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToNext()) { + AutocryptKeyStatus autocryptPeer = AutocryptPeer.KEY_STATUS_MAPPER.map(cursor); + result.add(autocryptPeer); + } + } + return result; + } + + public void insertOrUpdateLastSeen(String packageName, String autocryptId, Date date) { + UpdateLastSeen updateStatement = new UpdateLastSeen(db, AutocryptPeer.FACTORY); + updateStatement.bind(packageName, autocryptId, date); + int updated = updateStatement.executeUpdateDelete(); + + if (updated == 0) { + InsertPeer insertStatement = new InsertPeer(db, AutocryptPeer.FACTORY); + insertStatement.bind(packageName, autocryptId, date); + insertStatement.executeInsert(); + } + } + + public void updateKey(String packageName, String autocryptId, Date effectiveDate, long masterKeyId, + boolean isMutual) { + UpdateKey updateStatement = new UpdateKey(db, AutocryptPeer.FACTORY); + updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, isMutual); + int rowsUpdated = updateStatement.executeUpdateDelete(); + if (rowsUpdated == 0) { + throw new IllegalStateException("No rows updated! Was this peer inserted before the update?"); + } + databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId); + } + + public void updateKeyGossip(String packageName, String autocryptId, Date effectiveDate, long masterKeyId, + GossipOrigin origin) { + UpdateGossipKey updateStatement = new UpdateGossipKey(db, AutocryptPeer.FACTORY); + updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, origin); + int rowsUpdated = updateStatement.executeUpdateDelete(); + if (rowsUpdated == 0) { + throw new IllegalStateException("No rows updated! Was this peer inserted before the update?"); + } + databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId); + } + + public List getAutocryptPeersForKey(long masterKeyId) { + ArrayList result = new ArrayList<>(); + SqlDelightQuery query = AutocryptPeer.FACTORY.selectByMasterKeyId(masterKeyId); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToNext()) { + AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor); + result.add(autocryptPeer); + } + } + return result; + } + + public void deleteByIdentifier(String packageName, String autocryptId) { + Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId); + DeleteByIdentifier deleteStatement = new DeleteByIdentifier(db); + deleteStatement.bind(packageName, autocryptId); + deleteStatement.execute(); + if (masterKeyId != null) { + databaseNotifyManager.notifyAutocryptDelete(autocryptId, masterKeyId); + } + } + + public void deleteByMasterKeyId(long masterKeyId) { + DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db); + deleteStatement.bind(masterKeyId); + deleteStatement.execute(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java deleted file mode 100644 index d7d4707dd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.provider; - - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.support.annotation.Nullable; -import android.text.format.DateUtils; - -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; - - -public class AutocryptPeerDataAccessObject { - private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS; - - private static final String[] PROJECTION_LAST_SEEN = { ApiAutocryptPeer.LAST_SEEN }; - private static final String[] PROJECTION_LAST_SEEN_KEY = { ApiAutocryptPeer.LAST_SEEN_KEY }; - private static final String[] PROJECTION_GOSSIP_LAST_SEEN_KEY = { ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY }; - private static final String[] PROJECTION_MASTER_KEY_ID = { ApiAutocryptPeer.MASTER_KEY_ID }; - - private static final String[] PROJECTION_AUTOCRYPT_QUERY = { - ApiAutocryptPeer.IDENTIFIER, - ApiAutocryptPeer.LAST_SEEN, - ApiAutocryptPeer.MASTER_KEY_ID, - ApiAutocryptPeer.LAST_SEEN_KEY, - ApiAutocryptPeer.IS_MUTUAL, - ApiAutocryptPeer.KEY_IS_REVOKED, - ApiAutocryptPeer.KEY_IS_EXPIRED, - ApiAutocryptPeer.KEY_IS_VERIFIED, - ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, - ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, - ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED, - ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED, - ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED, - }; - private static final int INDEX_IDENTIFIER = 0; - private static final int INDEX_LAST_SEEN = 1; - private static final int INDEX_MASTER_KEY_ID = 2; - private static final int INDEX_LAST_SEEN_KEY = 3; - private static final int INDEX_STATE = 4; - private static final int INDEX_KEY_IS_REVOKED = 5; - private static final int INDEX_KEY_IS_EXPIRED = 6; - private static final int INDEX_KEY_IS_VERIFIED = 7; - private static final int INDEX_GOSSIP_MASTER_KEY_ID = 8; - private static final int INDEX_GOSSIP_LAST_SEEN_KEY = 9; - private static final int INDEX_GOSSIP_KEY_IS_REVOKED = 10; - private static final int INDEX_GOSSIP_KEY_IS_EXPIRED = 11; - private static final int INDEX_GOSSIP_KEY_IS_VERIFIED = 12; - - private final SimpleContentResolverInterface queryInterface; - private final String packageName; - private final DatabaseNotifyManager databaseNotifyManager; - - public AutocryptPeerDataAccessObject(Context context, String packageName) { - this.packageName = packageName; - this.databaseNotifyManager = DatabaseNotifyManager.create(context); - - final ContentResolver contentResolver = context.getContentResolver(); - queryInterface = new SimpleContentResolverInterface() { - @Override - public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Uri insert(Uri contentUri, ContentValues values) { - return contentResolver.insert(contentUri, values); - } - - @Override - public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) { - return contentResolver.update(contentUri, values, where, selectionArgs); - } - - @Override - public int delete(Uri contentUri, String where, String[] selectionArgs) { - return contentResolver.delete(contentUri, where, selectionArgs); - } - }; - } - - public AutocryptPeerDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName, - DatabaseNotifyManager databaseNotifyManager) { - this.queryInterface = queryInterface; - this.packageName = packageName; - this.databaseNotifyManager = databaseNotifyManager; - } - - public Long getMasterKeyIdForAutocryptPeer(String autocryptId) { - Cursor cursor = queryInterface.query( - ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), - PROJECTION_MASTER_KEY_ID, null, null, null); - - try { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return null; - } - - public Date getLastSeen(String autocryptId) { - Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), - PROJECTION_LAST_SEEN, null, null, null); - - try { - if (cursor != null && cursor.moveToFirst()) { - long lastUpdated = cursor.getLong(0); - return new Date(lastUpdated); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - public Date getLastSeenKey(String autocryptId) { - Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), - PROJECTION_LAST_SEEN_KEY, null, null, null); - - try { - if (cursor != null && cursor.moveToFirst()) { - long lastUpdated = cursor.getLong(0); - return new Date(lastUpdated); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - public Date getLastSeenGossip(String autocryptId) { - Cursor cursor = queryInterface.query(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), - PROJECTION_GOSSIP_LAST_SEEN_KEY, null, null, null); - - try { - if (cursor != null && cursor.moveToFirst()) { - long lastUpdated = cursor.getLong(0); - return new Date(lastUpdated); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - return null; - } - - public void updateLastSeen(String autocryptId, Date date) { - ContentValues cv = new ContentValues(); - cv.put(ApiAutocryptPeer.LAST_SEEN, date.getTime()); - queryInterface - .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); - } - - public void updateKey(String autocryptId, Date effectiveDate, long masterKeyId, boolean isMutual) { - ContentValues cv = new ContentValues(); - cv.put(ApiAutocryptPeer.MASTER_KEY_ID, masterKeyId); - cv.put(ApiAutocryptPeer.LAST_SEEN_KEY, effectiveDate.getTime()); - cv.put(ApiAutocryptPeer.IS_MUTUAL, isMutual ? 1 : 0); - queryInterface - .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); - databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId); - } - - public void updateKeyGossipFromAutocrypt(String autocryptId, Date effectiveDate, long masterKeyId) { - updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_AUTOCRYPT); - } - - public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) { - updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_SIGNATURE); - } - - public void updateKeyGossipFromDedup(String autocryptId, Date effectiveDate, long masterKeyId) { - updateKeyGossip(autocryptId, effectiveDate, masterKeyId, ApiAutocryptPeer.GOSSIP_ORIGIN_DEDUP); - } - - private void updateKeyGossip(String autocryptId, Date effectiveDate, long masterKeyId, int origin) { - ContentValues cv = new ContentValues(); - cv.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, masterKeyId); - cv.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, effectiveDate.getTime()); - cv.put(ApiAutocryptPeer.GOSSIP_ORIGIN, origin); - queryInterface - .update(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), cv, null, null); - databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId); - } - - public void delete(String autocryptId) { - Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId); - queryInterface.delete(ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptId), null, null); - databaseNotifyManager.notifyAutocryptDelete(autocryptId, masterKeyId); - } - - public List determineAutocryptRecommendations(String... autocryptIds) { - List result = new ArrayList<>(autocryptIds.length); - - Cursor cursor = queryAutocryptPeerData(autocryptIds); - try { - while (cursor.moveToNext()) { - AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(cursor); - result.add(peerResult); - } - } finally { - cursor.close(); - } - - return result; - } - - /** Determines Autocrypt "ui-recommendation", according to spec. - * See https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages - */ - private AutocryptRecommendationResult determineAutocryptRecommendation(Cursor cursor) { - String peerId = cursor.getString(INDEX_IDENTIFIER); - - AutocryptRecommendationResult keyRecommendation = determineAutocryptKeyRecommendation(peerId, cursor); - if (keyRecommendation != null) return keyRecommendation; - - AutocryptRecommendationResult gossipRecommendation = determineAutocryptGossipRecommendation(peerId, cursor); - if (gossipRecommendation != null) return gossipRecommendation; - - return new AutocryptRecommendationResult(peerId, AutocryptState.DISABLE, null, false); - } - - @Nullable - private AutocryptRecommendationResult determineAutocryptKeyRecommendation(String peerId, Cursor cursor) { - boolean hasKey = !cursor.isNull(INDEX_MASTER_KEY_ID); - boolean isRevoked = cursor.getInt(INDEX_KEY_IS_REVOKED) != 0; - boolean isExpired = cursor.getInt(INDEX_KEY_IS_EXPIRED) != 0; - if (!hasKey || isRevoked || isExpired) { - return null; - } - - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - long lastSeen = cursor.getLong(INDEX_LAST_SEEN); - long lastSeenKey = cursor.getLong(INDEX_LAST_SEEN_KEY); - boolean isVerified = cursor.getInt(INDEX_KEY_IS_VERIFIED) != 0; - if (lastSeenKey < (lastSeen - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) { - return new AutocryptRecommendationResult(peerId, AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified); - } - - boolean isMutual = cursor.getInt(INDEX_STATE) != 0; - if (isMutual) { - return new AutocryptRecommendationResult(peerId, AutocryptState.MUTUAL, masterKeyId, isVerified); - } else { - return new AutocryptRecommendationResult(peerId, AutocryptState.AVAILABLE, masterKeyId, isVerified); - } - } - - @Nullable - private AutocryptRecommendationResult determineAutocryptGossipRecommendation(String peerId, Cursor cursor) { - boolean gossipHasKey = !cursor.isNull(INDEX_GOSSIP_MASTER_KEY_ID); - boolean gossipIsRevoked = cursor.getInt(INDEX_GOSSIP_KEY_IS_REVOKED) != 0; - boolean gossipIsExpired = cursor.getInt(INDEX_GOSSIP_KEY_IS_EXPIRED) != 0; - boolean isVerified = cursor.getInt(INDEX_GOSSIP_KEY_IS_VERIFIED) != 0; - - if (!gossipHasKey || gossipIsRevoked || gossipIsExpired) { - return null; - } - - long masterKeyId = cursor.getLong(INDEX_GOSSIP_MASTER_KEY_ID); - return new AutocryptRecommendationResult(peerId, AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified); - } - - private Cursor queryAutocryptPeerData(String[] autocryptIds) { - StringBuilder selection = new StringBuilder(ApiAutocryptPeer.IDENTIFIER + " IN (?"); - for (int i = 1; i < autocryptIds.length; i++) { - selection.append(",?"); - } - selection.append(")"); - - return queryInterface.query(ApiAutocryptPeer.buildByPackageName(packageName), - PROJECTION_AUTOCRYPT_QUERY, selection.toString(), autocryptIds, null); - } - - public static class AutocryptRecommendationResult { - public final String peerId; - public final Long masterKeyId; - public final AutocryptState autocryptState; - public final boolean isVerified; - - AutocryptRecommendationResult(String peerId, AutocryptState autocryptState, Long masterKeyId, - boolean isVerified) { - this.peerId = peerId; - this.autocryptState = autocryptState; - this.masterKeyId = masterKeyId; - this.isVerified = isVerified; - } - - } - - public enum AutocryptState { - DISABLE, DISCOURAGED_OLD, DISCOURAGED_GOSSIP, AVAILABLE, MUTUAL - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index d2e1af3e4..a4eed8d49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -55,7 +55,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -84,29 +83,34 @@ public class KeyWritableRepository extends KeyRepository { private final Context context; private final DatabaseNotifyManager databaseNotifyManager; + private AutocryptPeerDao autocryptPeerDao; public static KeyWritableRepository create(Context context) { LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager); + AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); + return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, + autocryptPeerDao); } @VisibleForTesting KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, - DatabaseNotifyManager databaseNotifyManager) { - this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0); + DatabaseNotifyManager databaseNotifyManager, AutocryptPeerDao autocryptPeerDao) { + this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0, + autocryptPeerDao); } private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, - OperationLog log, int indent) { + OperationLog log, int indent, AutocryptPeerDao autocryptPeerDao) { super(context.getContentResolver(), localPublicKeyStorage, localSecretKeyStorage, log, indent); this.context = context; this.databaseNotifyManager = databaseNotifyManager; + this.autocryptPeerDao = autocryptPeerDao; } private LongSparseArray getTrustedMasterKeys() { @@ -585,7 +589,7 @@ public class KeyWritableRepository extends KeyRepository { Timber.e(e, "Could not delete file!"); return false; } - contentResolver.delete(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId),null, null); + autocryptPeerDao.deleteByMasterKeyId(masterKeyId); int deletedRows = contentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); databaseNotifyManager.notifyKeyChange(masterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index a44da1370..e41e8ed39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -51,13 +51,6 @@ public class KeychainContract { String EXPIRY = "expiry"; } - interface UpdatedKeysColumns { - String MASTER_KEY_ID = "master_key_id"; // not a database id - String LAST_UPDATED = "last_updated"; // time since epoch in seconds - String SEEN_ON_KEYSERVERS = "seen_on_keyservers"; - String FINGERPRINT = "fingerprint"; - } - interface KeySignaturesColumns { String MASTER_KEY_ID = "master_key_id"; // not a database id String SIGNER_KEY_ID = "signer_key_id"; @@ -86,11 +79,6 @@ public class KeychainContract { String DATA = "data"; } - interface ApiAppsColumns { - String PACKAGE_NAME = "package_name"; - String PACKAGE_CERTIFICATE = "package_signature"; - } - interface ApiAppsAllowedKeysColumns { String KEY_ID = "key_id"; // not a database id String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name @@ -100,20 +88,6 @@ public class KeychainContract { String IDENTIFIER = "identifier"; } - interface ApiAutocryptPeerColumns { - String PACKAGE_NAME = "package_name"; - String IDENTIFIER = "identifier"; - String LAST_SEEN = "last_seen"; - - String MASTER_KEY_ID = "master_key_id"; - String LAST_SEEN_KEY = "last_seen_key"; - String IS_MUTUAL = "is_mutual"; - - String GOSSIP_MASTER_KEY_ID = "gossip_master_key_id"; - String GOSSIP_LAST_SEEN_KEY = "gossip_last_seen_key"; - String GOSSIP_ORIGIN = "gossip_origin"; - } - public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY; private static final Uri BASE_CONTENT_URI_INTERNAL = Uri @@ -121,8 +95,6 @@ public class KeychainContract { public static final String BASE_KEY_RINGS = "key_rings"; - public static final String BASE_UPDATED_KEYS = "updated_keys"; - public static final String BASE_KEY_SIGNATURES = "key_signatures"; public static final String PATH_UNIFIED = "unified"; @@ -141,11 +113,6 @@ public class KeychainContract { public static final String PATH_KEYS = "keys"; public static final String PATH_CERTS = "certs"; - public static final String PATH_BY_PACKAGE_NAME = "by_package_name"; - public static final String PATH_BY_KEY_ID = "by_key_id"; - - public static final String BASE_AUTOCRYPT_PEERS = "autocrypt_peers"; - public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; public static final String IS_REVOKED = KeysColumns.IS_REVOKED; @@ -310,37 +277,6 @@ public class KeychainContract { } - public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_AUTOCRYPT_PEERS).build(); - public static final String KEY_IS_REVOKED = "key_is_revoked"; - public static final String KEY_IS_EXPIRED = "key_is_expired"; - public static final String KEY_IS_VERIFIED = "key_is_verified"; - public static final String GOSSIP_KEY_IS_REVOKED = "gossip_key_is_revoked"; - public static final String GOSSIP_KEY_IS_EXPIRED = "gossip_key_is_expired"; - public static final String GOSSIP_KEY_IS_VERIFIED = "gossip_key_is_verified"; - - public static final int GOSSIP_ORIGIN_AUTOCRYPT = 0; - public static final int GOSSIP_ORIGIN_SIGNATURE = 10; - public static final int GOSSIP_ORIGIN_DEDUP = 20; - - public static Uri buildByKeyUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(uri.getPathSegments().get(1)).build(); - } - - public static Uri buildByPackageName(String packageName) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).build(); - } - - public static Uri buildByPackageNameAndAutocryptId(String packageName, String autocryptPeer) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName).appendPath(autocryptPeer).build(); - } - - public static Uri buildByMasterKeyId(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(Long.toString(masterKeyId)).build(); - } - } - public static class Certs implements CertsColumns, BaseColumns { public static final String USER_ID = UserPacketsColumns.USER_ID; public static final String NAME = UserPacketsColumns.NAME; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 81861e9e8..cd925c115 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -30,21 +30,21 @@ import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration; import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory; import android.content.Context; import android.database.Cursor; +import android.database.SQLException; import android.database.sqlite.SQLiteException; import android.provider.BaseColumns; +import org.sufficientlysecure.keychain.AutocryptPeersModel; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeyMetadataModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignaturesColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; @@ -60,20 +60,18 @@ import timber.log.Timber; */ public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 27; + private static final int DATABASE_VERSION = 28; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; private Context context; public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; String KEYS = "keys"; - String UPDATED_KEYS = "updated_keys"; String KEY_SIGNATURES = "key_signatures"; String USER_PACKETS = "user_packets"; String CERTS = "certs"; String API_ALLOWED_KEYS = "api_allowed_keys"; String OVERRIDDEN_WARNINGS = "overridden_warnings"; - String API_AUTOCRYPT_PEERS = "api_autocrypt_peers"; } private static final String CREATE_KEYS = @@ -151,23 +149,6 @@ public class KeychainDatabase { + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; - private static final String CREATE_API_AUTOCRYPT_PEERS = - "CREATE TABLE IF NOT EXISTS " + Tables.API_AUTOCRYPT_PEERS + " (" - + ApiAutocryptPeerColumns.PACKAGE_NAME + " TEXT NOT NULL, " - + ApiAutocryptPeerColumns.IDENTIFIER + " TEXT NOT NULL, " - + ApiAutocryptPeerColumns.LAST_SEEN + " INTEGER, " - + ApiAutocryptPeerColumns.LAST_SEEN_KEY + " INTEGER NULL, " - + ApiAutocryptPeerColumns.IS_MUTUAL + " INTEGER NULL, " - + ApiAutocryptPeerColumns.MASTER_KEY_ID + " INTEGER NULL, " - + ApiAutocryptPeerColumns.GOSSIP_MASTER_KEY_ID + " INTEGER NULL, " - + ApiAutocryptPeerColumns.GOSSIP_LAST_SEEN_KEY + " INTEGER NULL, " - + ApiAutocryptPeerColumns.GOSSIP_ORIGIN + " INTEGER NULL, " - + "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", " - + ApiAutocryptPeerColumns.IDENTIFIER + "), " - + "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES " - + "api_apps (package_signature) ON DELETE CASCADE" - + ")"; - private static final String CREATE_API_APPS_ALLOWED_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " @@ -237,7 +218,7 @@ public class KeychainDatabase { db.execSQL(CREATE_KEY_SIGNATURES); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); - db.execSQL(CREATE_API_AUTOCRYPT_PEERS); + db.execSQL(AutocryptPeersModel.CREATE_TABLE); db.execSQL(ApiApp.CREATE_TABLE); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); @@ -466,6 +447,10 @@ public class KeychainDatabase { case 26: { migrateUpdatedKeysToKeyMetadataTable(db); } + + case 27: { + renameApiAutocryptPeersTable(db); + } } } @@ -488,6 +473,10 @@ public class KeychainDatabase { db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;"); } + private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;"); + } + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { // Downgrade is ok for the debug version, makes it easier to work with branches if (Constants.DEBUG) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java index e19332d92..4802b667e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -22,7 +22,6 @@ import android.net.Uri; import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; public class KeychainExternalContract { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 805dd8036..6a02f5702 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -38,8 +38,8 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.model.AutocryptPeer; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -74,10 +74,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RINGS_FIND_BY_USER_ID = 402; private static final int KEY_RINGS_FILTER_BY_SIGNER = 403; - private static final int AUTOCRYPT_PEERS_BY_MASTER_KEY_ID = 601; - private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME = 602; - private static final int AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID = 603; - private static final int KEY_SIGNATURES = 700; protected UriMatcher mUriMatcher; @@ -173,21 +169,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe + KeychainContract.PATH_CERTS + "/*/*", KEY_RING_CERTS_SPECIFIC); - /* - * Trust Identity access - * - *
-         * trust_ids/by_key_id/_
-         *
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" + - KeychainContract.PATH_BY_KEY_ID + "/*", AUTOCRYPT_PEERS_BY_MASTER_KEY_ID); - matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" + - KeychainContract.PATH_BY_PACKAGE_NAME + "/*", AUTOCRYPT_PEERS_BY_PACKAGE_NAME); - matcher.addURI(authority, KeychainContract.BASE_AUTOCRYPT_PEERS + "/" + - KeychainContract.PATH_BY_PACKAGE_NAME + "/*/*", AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_SIGNATURES, KEY_SIGNATURES); @@ -309,7 +290,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY + " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); projectionMap.put(KeyRings.API_KNOWN_TO_PACKAGE_NAMES, - "GROUP_CONCAT(DISTINCT aTI." + ApiAutocryptPeer.PACKAGE_NAME + ") AS " + "GROUP_CONCAT(DISTINCT aTI." + AutocryptPeer.PACKAGE_NAME + ") AS " + KeyRings.API_KNOWN_TO_PACKAGE_NAMES); qb.setProjectionMap(projectionMap); @@ -390,8 +371,8 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe + " >= " + new Date().getTime() / 1000 + " )" + ")" : "") + (plist.contains(KeyRings.API_KNOWN_TO_PACKAGE_NAMES) ? - " LEFT JOIN " + Tables.API_AUTOCRYPT_PEERS + " AS aTI ON (" - +"aTI." + Keys.MASTER_KEY_ID + " LEFT JOIN " + AutocryptPeer.TABLE_NAME + " AS aTI ON (" + +"aTI." + AutocryptPeer.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + ")" : "") ); @@ -639,76 +620,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case AUTOCRYPT_PEERS_BY_MASTER_KEY_ID: - case AUTOCRYPT_PEERS_BY_PACKAGE_NAME: - case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { - long unixSeconds = new Date().getTime() / 1000; - - HashMap projectionMap = new HashMap<>(); - projectionMap.put(ApiAutocryptPeer._ID, Tables.API_AUTOCRYPT_PEERS + ".oid AS " + ApiAutocryptPeer._ID); - projectionMap.put(ApiAutocryptPeer.IDENTIFIER, ApiAutocryptPeer.IDENTIFIER); - projectionMap.put(ApiAutocryptPeer.PACKAGE_NAME, ApiAutocryptPeer.PACKAGE_NAME); - projectionMap.put(ApiAutocryptPeer.LAST_SEEN, ApiAutocryptPeer.LAST_SEEN); - projectionMap.put(ApiAutocryptPeer.MASTER_KEY_ID, Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID); - projectionMap.put(ApiAutocryptPeer.IS_MUTUAL, ApiAutocryptPeer.IS_MUTUAL); - projectionMap.put(ApiAutocryptPeer.LAST_SEEN_KEY, ApiAutocryptPeer.LAST_SEEN_KEY); - projectionMap.put(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID, ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID); - projectionMap.put(ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY, ApiAutocryptPeer.GOSSIP_LAST_SEEN_KEY); - projectionMap.put(ApiAutocryptPeer.GOSSIP_ORIGIN, ApiAutocryptPeer.GOSSIP_ORIGIN); - projectionMap.put(ApiAutocryptPeer.KEY_IS_REVOKED, "ac_key." + Keys.IS_REVOKED + " AS " + ApiAutocryptPeer.KEY_IS_REVOKED); - projectionMap.put(ApiAutocryptPeer.KEY_IS_EXPIRED, "(CASE" + - " WHEN ac_key." + Keys.EXPIRY + " IS NULL THEN 0" + - " WHEN ac_key." + Keys.EXPIRY + " > " + unixSeconds + " THEN 0" + - " ELSE 1 END) AS " + ApiAutocryptPeer.KEY_IS_EXPIRED); - projectionMap.put(ApiAutocryptPeer.KEY_IS_VERIFIED, "EXISTS (SELECT * FROM " + Tables.CERTS + - " WHERE " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + - Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + - " AND " + Certs.VERIFIED + " = " + Certs.VERIFIED_SECRET + - ") AS " + ApiAutocryptPeer.KEY_IS_VERIFIED); - projectionMap.put(ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED, - "gossip_key." + Keys.IS_REVOKED + " AS " + ApiAutocryptPeer.GOSSIP_KEY_IS_REVOKED); - projectionMap.put(ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED, "(CASE" + - " WHEN gossip_key." + Keys.EXPIRY + " IS NULL THEN 0" + - " WHEN gossip_key." + Keys.EXPIRY + " > " + unixSeconds + " THEN 0" + - " ELSE 1 END) AS " + ApiAutocryptPeer.GOSSIP_KEY_IS_EXPIRED); - projectionMap.put(ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED, "EXISTS (SELECT * FROM " + Tables.CERTS + - " WHERE " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + - Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID + - " AND " + Certs.VERIFIED + " = " + Certs.VERIFIED_SECRET + - ") AS " + ApiAutocryptPeer.GOSSIP_KEY_IS_VERIFIED); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.API_AUTOCRYPT_PEERS + - " LEFT JOIN " + Tables.KEYS + " AS ac_key" + - " ON (ac_key." + Keys.MASTER_KEY_ID + " = " + Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + - " AND ac_key." + Keys.RANK + " = 0)" + - " LEFT JOIN " + Tables.KEYS + " AS gossip_key" + - " ON (gossip_key." + Keys.MASTER_KEY_ID + " = " + ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID + - " AND gossip_key." + Keys.RANK + " = 0)" - ); - - if (match == AUTOCRYPT_PEERS_BY_MASTER_KEY_ID) { - long masterKeyId = Long.parseLong(uri.getLastPathSegment()); - - qb.appendWhere(Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.MASTER_KEY_ID + " = "); - qb.appendWhere(Long.toString(masterKeyId)); - } else if (match == AUTOCRYPT_PEERS_BY_PACKAGE_NAME) { - String packageName = uri.getPathSegments().get(2); - - qb.appendWhere(Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = "); - qb.appendWhereEscapeString(packageName); - } else { // AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID - String packageName = uri.getPathSegments().get(2); - String autocryptPeer = uri.getPathSegments().get(3); - - selection = Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.PACKAGE_NAME + " = ? AND " + - Tables.API_AUTOCRYPT_PEERS + "." + ApiAutocryptPeer.IDENTIFIER + " = ?"; - selectionArgs = new String[] { packageName, autocryptPeer }; - } - - break; - } - default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -846,25 +757,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { - String packageName = uri.getPathSegments().get(2); - String autocryptPeer = uri.getPathSegments().get(3); - - String selection = ApiAutocryptPeer.PACKAGE_NAME + " = ? AND " + ApiAutocryptPeer.IDENTIFIER + " = ?"; - selectionArgs = new String[] { packageName, autocryptPeer }; - - count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs); - break; - } - - case AUTOCRYPT_PEERS_BY_MASTER_KEY_ID: - String selection = ApiAutocryptPeer.MASTER_KEY_ID + " = " + uri.getLastPathSegment(); - if (!TextUtils.isEmpty(additionalSelection)) { - selection += " AND (" + additionalSelection + ")"; - } - count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs); - break; - default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -900,21 +792,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs); break; } - case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { - String packageName = uri.getPathSegments().get(2); - String identifier = uri.getLastPathSegment(); - values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); - values.put(ApiAutocryptPeer.IDENTIFIER, identifier); - - int updated = db.update(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_IGNORE, values, - ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?", - new String[] { packageName, identifier }); - if (updated == 0) { - db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values); - } - - break; - } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index c981f5df5..d280f6a11 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -2,52 +2,64 @@ package org.sufficientlysecure.keychain.remote; import java.io.IOException; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import android.content.Context; import android.support.annotation.Nullable; +import android.text.format.DateUtils; import org.openintents.openpgp.AutocryptPeerUpdate; import org.openintents.openpgp.AutocryptPeerUpdate.PreferEncrypt; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.model.AutocryptPeer; +import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus; +import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import timber.log.Timber; -class AutocryptInteractor { +public class AutocryptInteractor { + private static final long AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS = 35 * DateUtils.DAY_IN_MILLIS; - private AutocryptPeerDataAccessObject autocryptPeerDao; + private AutocryptPeerDao autocryptPeerDao; private KeyWritableRepository keyWritableRepository; - public static AutocryptInteractor getInstance(Context context, AutocryptPeerDataAccessObject autocryptPeerentityDao) { + private final String packageName; + + public static AutocryptInteractor getInstance(Context context, String packageName) { + AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(context); - return new AutocryptInteractor(autocryptPeerentityDao, keyWritableRepository); + return new AutocryptInteractor(autocryptPeerDao, keyWritableRepository, packageName); } - private AutocryptInteractor(AutocryptPeerDataAccessObject autocryptPeerDao, - KeyWritableRepository keyWritableRepository) { + private AutocryptInteractor(AutocryptPeerDao autocryptPeerDao, + KeyWritableRepository keyWritableRepository, String packageName) { this.autocryptPeerDao = autocryptPeerDao; this.keyWritableRepository = keyWritableRepository; + this.packageName = packageName; } void updateAutocryptPeerState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) { + AutocryptPeer currentAutocryptPeer = autocryptPeerDao.getAutocryptPeer(packageName, autocryptPeerId); Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); // 1. If the message’s effective date is older than the peers[from-addr].autocrypt_timestamp value, then no changes are required, and the update process terminates. - Date lastSeenAutocrypt = autocryptPeerDao.getLastSeenKey(autocryptPeerId); - if (lastSeenAutocrypt != null && effectiveDate.compareTo(lastSeenAutocrypt) <= 0) { + Date lastSeenKey = currentAutocryptPeer != null ? currentAutocryptPeer.last_seen_key() : null; + if (lastSeenKey != null && effectiveDate.compareTo(lastSeenKey) <= 0) { return; } // 2. If the message’s effective date is more recent than peers[from-addr].last_seen then set peers[from-addr].last_seen to the message’s effective date. - Date lastSeen = autocryptPeerDao.getLastSeen(autocryptPeerId); + Date lastSeen = currentAutocryptPeer != null ? currentAutocryptPeer.last_seen() : null; if (lastSeen == null || effectiveDate.after(lastSeen)) { - autocryptPeerDao.updateLastSeen(autocryptPeerId, effectiveDate); + autocryptPeerDao.insertOrUpdateLastSeen(packageName, autocryptPeerId, effectiveDate); } // 3. If the Autocrypt header is unavailable, no further changes are required and the update process terminates. @@ -66,17 +78,18 @@ class AutocryptInteractor { // 6. Set peers[from-addr].prefer_encrypt to the corresponding prefer-encrypt value of the Autocrypt header. boolean isMutual = autocryptPeerUpdate.getPreferEncrypt() == PreferEncrypt.MUTUAL; - autocryptPeerDao.updateKey(autocryptPeerId, effectiveDate, newMasterKeyId, isMutual); + autocryptPeerDao.updateKey(packageName, autocryptPeerId, effectiveDate, newMasterKeyId, isMutual); } void updateAutocryptPeerGossipState(String autocryptPeerId, AutocryptPeerUpdate autocryptPeerUpdate) { + AutocryptPeer currentAutocryptPeer = autocryptPeerDao.getAutocryptPeer(packageName, autocryptPeerId); Date effectiveDate = autocryptPeerUpdate.getEffectiveDate(); // 1. If gossip-addr does not match any recipient in the mail’s To or Cc header, the update process terminates (i.e., header is ignored). // -> This should be taken care of in the mail client that sends us this data! // 2. If peers[gossip-addr].gossip_timestamp is more recent than the message’s effective date, then the update process terminates. - Date lastSeenGossip = autocryptPeerDao.getLastSeenGossip(autocryptPeerId); + Date lastSeenGossip = currentAutocryptPeer.gossip_last_seen_key(); if (lastSeenGossip != null && lastSeenGossip.after(effectiveDate)) { return; } @@ -94,7 +107,8 @@ class AutocryptInteractor { // 4. Set peers[gossip-addr].gossip_key to the value of the keydata attribute. Long newMasterKeyId = saveKeyringResult.savedMasterKeyId; - autocryptPeerDao.updateKeyGossipFromAutocrypt(autocryptPeerId, effectiveDate, newMasterKeyId); + autocryptPeerDao.updateKeyGossip(packageName, autocryptPeerId, effectiveDate, newMasterKeyId, + GossipOrigin.GOSSIP_HEADER); } @Nullable @@ -131,4 +145,97 @@ class AutocryptInteractor { } return uncachedKeyRing; } + + public List determineAutocryptRecommendations(String... autocryptIds) { + List result = new ArrayList<>(autocryptIds.length); + + for (AutocryptKeyStatus autocryptKeyStatus : autocryptPeerDao.getAutocryptKeyStatus(packageName, autocryptIds)) { + AutocryptRecommendationResult peerResult = determineAutocryptRecommendation(autocryptKeyStatus); + result.add(peerResult); + } + + return result; + } + + /** Determines Autocrypt "ui-recommendation", according to spec. + * See https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages + */ + private AutocryptRecommendationResult determineAutocryptRecommendation(AutocryptKeyStatus autocryptKeyStatus) { + AutocryptRecommendationResult keyRecommendation = determineAutocryptKeyRecommendation(autocryptKeyStatus); + if (keyRecommendation != null) return keyRecommendation; + + AutocryptRecommendationResult gossipRecommendation = determineAutocryptGossipRecommendation(autocryptKeyStatus); + if (gossipRecommendation != null) return gossipRecommendation; + + return new AutocryptRecommendationResult(autocryptKeyStatus.autocryptPeer().identifier(), AutocryptState.DISABLE, null, false); + } + + @Nullable + private AutocryptRecommendationResult determineAutocryptKeyRecommendation(AutocryptKeyStatus autocryptKeyStatus) { + boolean hasKey = autocryptKeyStatus.hasKey(); + boolean isRevoked = autocryptKeyStatus.isKeyRevoked(); + boolean isExpired = autocryptKeyStatus.isKeyExpired(); + if (!hasKey || isRevoked || isExpired) { + return null; + } + + AutocryptPeer autocryptPeer = autocryptKeyStatus.autocryptPeer(); + long masterKeyId = autocryptPeer.master_key_id(); + Date lastSeen = autocryptPeer.last_seen(); + Date lastSeenKey = autocryptPeer.last_seen_key(); + boolean isVerified = autocryptKeyStatus.isKeyVerified(); + if (lastSeenKey.getTime() < (lastSeen.getTime() - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) { + return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified); + } + + boolean isMutual = autocryptPeer.is_mutual(); + if (isMutual) { + return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.MUTUAL, masterKeyId, isVerified); + } else { + return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.AVAILABLE, masterKeyId, isVerified); + } + } + + @Nullable + private AutocryptRecommendationResult determineAutocryptGossipRecommendation(AutocryptKeyStatus autocryptKeyStatus) { + boolean gossipHasKey = autocryptKeyStatus.hasGossipKey(); + boolean gossipIsRevoked = autocryptKeyStatus.isGossipKeyRevoked(); + boolean gossipIsExpired = autocryptKeyStatus.isGossipKeyExpired(); + boolean isVerified = autocryptKeyStatus.isGossipKeyVerified(); + + if (!gossipHasKey || gossipIsRevoked || gossipIsExpired) { + return null; + } + + Long masterKeyId = autocryptKeyStatus.autocryptPeer().gossip_master_key_id(); + return new AutocryptRecommendationResult(autocryptKeyStatus.autocryptPeer().identifier(), AutocryptState.DISCOURAGED_GOSSIP, masterKeyId, isVerified); + } + + public void updateKeyGossipFromSignature(String autocryptId, Date effectiveDate, long masterKeyId) { + autocryptPeerDao.updateKeyGossip(packageName, autocryptId, effectiveDate, masterKeyId, GossipOrigin.SIGNATURE); + } + + public void updateKeyGossipFromDedup(String autocryptId, long masterKeyId) { + autocryptPeerDao.updateKeyGossip(packageName, autocryptId, new Date(), masterKeyId, GossipOrigin.DEDUP); + } + + public static class AutocryptRecommendationResult { + public final String peerId; + public final Long masterKeyId; + public final AutocryptState autocryptState; + public final boolean isVerified; + + AutocryptRecommendationResult(String peerId, AutocryptState autocryptState, Long masterKeyId, + boolean isVerified) { + this.peerId = peerId; + this.autocryptState = autocryptState; + this.masterKeyId = masterKeyId; + this.isVerified = isVerified; + } + + } + + public enum AutocryptState { + DISABLE, DISCOURAGED_OLD, DISCOURAGED_GOSSIP, AVAILABLE, MUTUAL + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 855babaaf..2d146fe77 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -39,10 +39,6 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState; -import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; @@ -52,8 +48,9 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; -import org.sufficientlysecure.keychain.provider.KeychainProvider; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; +import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult; +import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState; import timber.log.Timber; @@ -69,8 +66,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC private UriMatcher uriMatcher; private ApiPermissionHelper apiPermissionHelper; - private KeychainProvider internalKeychainProvider; - private DatabaseNotifyManager databaseNotifyManager; /** @@ -107,10 +102,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC throw new NullPointerException("Context can't be null during onCreate!"); } - internalKeychainProvider = new KeychainProvider(); - internalKeychainProvider.attachInfo(context, null); apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext())); - databaseNotifyManager = DatabaseNotifyManager.create(context); return true; } @@ -267,10 +259,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC throw new UnsupportedOperationException("Cannot wildcard-query autocrypt results!"); } if (!isWildcardSelector && queriesAutocryptResult) { - AutocryptPeerDataAccessObject autocryptPeerDao = - new AutocryptPeerDataAccessObject(internalKeychainProvider, callingPackageName, - databaseNotifyManager); - fillTempTableWithAutocryptRecommendations(db, autocryptPeerDao, selectionArgs); + AutocryptInteractor autocryptInteractor = + AutocryptInteractor.getInstance(getContext(), callingPackageName); + fillTempTableWithAutocryptRecommendations(db, autocryptInteractor, selectionArgs); } HashMap projectionMap = new HashMap<>(); @@ -310,7 +301,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC } qb.setStrict(true); - String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy); + String query = qb.buildQuery(projection, null, groupBy, null, orderBy, null); Cursor cursor = db.query(query); if (cursor != null) { // Tell the cursor what uri to watch, so it knows when its source data changes @@ -327,9 +318,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC } private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, - AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) { + AutocryptInteractor autocryptInteractor, String[] peerIds) { List autocryptStates = - autocryptPeerDao.determineAutocryptRecommendations(peerIds); + autocryptInteractor.determineAutocryptRecommendations(peerIds); fillTempTableWithAutocryptRecommendations(db, autocryptStates); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 8406c297b..2a7fe0874 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -68,10 +68,9 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; @@ -585,8 +584,8 @@ public class OpenPgpService extends Service { return signatureResult; } - AutocryptPeerDataAccessObject autocryptPeerentityDao = new AutocryptPeerDataAccessObject(getBaseContext(), - mApiPermissionHelper.getCurrentCallingPackage()); + AutocryptPeerDao autocryptPeerentityDao = + AutocryptPeerDao.getInstance(getBaseContext()); Long autocryptPeerMasterKeyId = autocryptPeerentityDao.getMasterKeyIdForAutocryptPeer(autocryptPeerId); long masterKeyId = signatureResult.getKeyId(); @@ -596,7 +595,9 @@ public class OpenPgpService extends Service { if (effectiveTime.after(now)) { effectiveTime = now; } - autocryptPeerentityDao.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId); + AutocryptInteractor autocryptInteractor = + AutocryptInteractor.getInstance(this, mApiPermissionHelper.getCurrentCallingPackage()); + autocryptInteractor.updateKeyGossipFromSignature(autocryptPeerId, effectiveTime, masterKeyId); return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.NEW); } else if (masterKeyId == autocryptPeerMasterKeyId) { return signatureResult.withAutocryptPeerResult(AutocryptPeerResult.OK); @@ -860,9 +861,8 @@ public class OpenPgpService extends Service { private Intent updateAutocryptPeerImpl(Intent data) { try { - AutocryptPeerDataAccessObject autocryptPeerDao = new AutocryptPeerDataAccessObject(getBaseContext(), - mApiPermissionHelper.getCurrentCallingPackage()); - AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance(getBaseContext(), autocryptPeerDao); + AutocryptInteractor autocryptInteractor = AutocryptInteractor.getInstance( + getBaseContext(), mApiPermissionHelper.getCurrentCallingPackage()); if (data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID) && data.hasExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index ffda9a81d..a3ecadbe1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -18,7 +18,6 @@ package org.sufficientlysecure.keychain.remote.ui.dialog; -import java.util.Date; import java.util.List; import android.content.Context; @@ -31,10 +30,10 @@ import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.remote.AutocryptInteractor; import timber.log.Timber; @@ -44,7 +43,7 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { private final int loaderId; - private AutocryptPeerDataAccessObject autocryptPeerDao; + private AutocryptInteractor autocryptInteractor; private String duplicateAddress; private RemoteDeduplicateView view; @@ -73,7 +72,7 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { return; } - autocryptPeerDao = new AutocryptPeerDataAccessObject(context, packageName); + this.autocryptInteractor = AutocryptInteractor.getInstance(context, packageName); this.duplicateAddress = duplicateAddress; view.setAddressText(duplicateAddress); @@ -121,8 +120,8 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { return; } - long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); - autocryptPeerDao.updateKeyGossipFromDedup(duplicateAddress, new Date(), masterKeyId); + long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); + autocryptInteractor.updateKeyGossipFromDedup(duplicateAddress, masterKeyId); view.finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index 8a3458a51..fe6609e5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -36,7 +36,8 @@ import com.google.auto.value.AutoValue; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.UriAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; +import org.sufficientlysecure.keychain.model.AutocryptPeer; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; @@ -71,30 +72,26 @@ public class IdentityDao { private static final String USER_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; - private static final String[] AUTOCRYPT_PEER_PROJECTION = new String[] { - ApiAutocryptPeer._ID, - ApiAutocryptPeer.PACKAGE_NAME, - ApiAutocryptPeer.IDENTIFIER, - }; - private static final int INDEX_PACKAGE_NAME = 1; - private static final int INDEX_IDENTIFIER = 2; - private final ContentResolver contentResolver; private final PackageIconGetter packageIconGetter; private final PackageManager packageManager; + private final AutocryptPeerDao autocryptPeerDao; static IdentityDao getInstance(Context context) { ContentResolver contentResolver = context.getContentResolver(); PackageManager packageManager = context.getPackageManager(); PackageIconGetter iconGetter = PackageIconGetter.getInstance(context); - return new IdentityDao(contentResolver, packageManager, iconGetter); + AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); + return new IdentityDao(contentResolver, packageManager, iconGetter, autocryptPeerDao); } - private IdentityDao(ContentResolver contentResolver, PackageManager packageManager, PackageIconGetter iconGetter) { + private IdentityDao(ContentResolver contentResolver, PackageManager packageManager, PackageIconGetter iconGetter, + AutocryptPeerDao autocryptPeerDao) { this.packageManager = packageManager; this.contentResolver = contentResolver; this.packageIconGetter = iconGetter; + this.autocryptPeerDao = autocryptPeerDao; } List getIdentityInfos(long masterKeyId, boolean showLinkedIds) { @@ -110,35 +107,24 @@ public class IdentityDao { } private void correlateOrAddAutocryptPeers(ArrayList identities, long masterKeyId) { - Cursor cursor = contentResolver.query(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId), - AUTOCRYPT_PEER_PROJECTION, null, null, null); - if (cursor == null) { - Timber.e("Error loading Autocrypt peers"); - return; - } + for (AutocryptPeer autocryptPeer : autocryptPeerDao.getAutocryptPeersForKey(masterKeyId)) { + String packageName = autocryptPeer.package_name(); + String autocryptId = autocryptPeer.identifier(); - try { - while (cursor.moveToNext()) { - String packageName = cursor.getString(INDEX_PACKAGE_NAME); - String autocryptPeer = cursor.getString(INDEX_IDENTIFIER); + Drawable drawable = packageIconGetter.getDrawableForPackageName(packageName); + Intent autocryptPeerIntent = getAutocryptPeerActivityIntentIfResolvable(packageName, autocryptId); - Drawable drawable = packageIconGetter.getDrawableForPackageName(packageName); - Intent autocryptPeerIntent = getAutocryptPeerActivityIntentIfResolvable(packageName, autocryptPeer); - - UserIdInfo associatedUserIdInfo = findUserIdMatchingAutocryptPeer(identities, autocryptPeer); - if (associatedUserIdInfo != null) { - int position = identities.indexOf(associatedUserIdInfo); - AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo - .create(associatedUserIdInfo, autocryptPeer, packageName, drawable, autocryptPeerIntent); - identities.set(position, autocryptPeerInfo); - } else { - AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo - .create(autocryptPeer, packageName, drawable, autocryptPeerIntent); - identities.add(autocryptPeerInfo); - } + UserIdInfo associatedUserIdInfo = findUserIdMatchingAutocryptPeer(identities, autocryptId); + if (associatedUserIdInfo != null) { + int position = identities.indexOf(associatedUserIdInfo); + AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo + .create(associatedUserIdInfo, autocryptId, packageName, drawable, autocryptPeerIntent); + identities.set(position, autocryptPeerInfo); + } else { + AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo + .create(autocryptId, packageName, drawable, autocryptPeerIntent); + identities.add(autocryptPeerInfo); } - } finally { - cursor.close(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java index 28cd69b5b..478d0be5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java @@ -29,7 +29,7 @@ import android.net.Uri; import android.support.annotation.Nullable; import android.view.View; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; @@ -56,12 +56,14 @@ public class IdentitiesPresenter implements Observer> { private final long masterKeyId; private final boolean isSecret; private final boolean showLinkedIds; + private AutocryptPeerDao autocryptPeerDao; public IdentitiesPresenter(Context context, IdentitiesMvpView view, ViewKeyMvpView viewKeyMvpView, long masterKeyId, boolean isSecret) { this.context = context; this.view = view; this.viewKeyMvpView = viewKeyMvpView; + this.autocryptPeerDao = AutocryptPeerDao.getInstance(context); this.masterKeyId = masterKeyId; this.isSecret = isSecret; @@ -146,9 +148,7 @@ public class IdentitiesPresenter implements Observer> { return; } - AutocryptPeerDataAccessObject autocryptPeerDao = - new AutocryptPeerDataAccessObject(context, info.getPackageName()); - autocryptPeerDao.delete(info.getIdentity()); + autocryptPeerDao.deleteByIdentifier(info.getPackageName(), info.getIdentity()); } public LiveData> getLiveDataInstance() { diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq new file mode 100644 index 000000000..df4506516 --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq @@ -0,0 +1,64 @@ +import java.util.Date; +import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; + +CREATE TABLE IF NOT EXISTS autocrypt_peers ( + package_name TEXT NOT NULL, + identifier TEXT NOT NULL, + last_seen INTEGER AS Date NULL, + last_seen_key INTEGER AS Date NULL, + is_mutual INTEGER AS Boolean NOT NULL DEFAULT 0, + master_key_id INTEGER NULL, + gossip_master_key_id INTEGER NULL, + gossip_last_seen_key INTEGER AS Date NULL, + gossip_origin INTEGER AS GossipOrigin NULL, + PRIMARY KEY(package_name, identifier), + FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE +); + +selectByIdentifiers: +SELECT * + FROM autocrypt_peers + WHERE package_name = ? AND identifier IN ?; + +selectByMasterKeyId: +SELECT * + FROM autocrypt_peers + WHERE master_key_id = ?; + +selectMasterKeyIdByIdentifier: +SELECT master_key_id + FROM autocrypt_peers + WHERE identifier = ?; + +deleteByIdentifier: +DELETE FROM autocrypt_peers + WHERE package_name = ? AND identifier = ?; + +deleteByMasterKeyId: +DELETE FROM autocrypt_peers + WHERE master_key_id = ?; + +updateLastSeen: +UPDATE autocrypt_peers SET last_seen = ?3 WHERE package_name = ?1 AND identifier = ?2; + +updateKey: +UPDATE autocrypt_peers SET last_seen_key = ?3, master_key_id = ?4, is_mutual = ?5 WHERE package_name = ?1 AND identifier = ?2; + +updateGossipKey: +UPDATE autocrypt_peers SET gossip_last_seen_key = ?3, gossip_master_key_id = ?4, gossip_origin = ?5 WHERE package_name = ?1 AND identifier = ?2; + +insertPeer: +INSERT INTO autocrypt_peers (package_name, identifier, last_seen) VALUES (?, ?, ?); + +selectAutocryptKeyStatus: +SELECT autocryptPeer.*, + (CASE WHEN ac_key.expiry IS NULL THEN 0 WHEN ac_key.expiry > ?3 THEN 0 ELSE 1 END) AS key_is_expired_int, + (CASE WHEN gossip_key.expiry IS NULL THEN 0 WHEN gossip_key.expiry > ?3 THEN 0 ELSE 1 END) AS gossip_key_is_expired_int, + ac_key.is_revoked AS key_is_revoked_int, + gossip_key.is_revoked AS gossip_key_is_revoked_int, + EXISTS (SELECT * FROM certs WHERE certs.master_key_id = autocryptPeer.master_key_id AND verified = 1 ) AS key_is_verified_int, + EXISTS (SELECT * FROM certs WHERE certs.master_key_id = autocryptPeer.gossip_master_key_id AND verified = 1 ) AS gossip_key_is_verified_int + FROM autocrypt_peers AS autocryptPeer + LEFT JOIN keys AS ac_key ON (ac_key.master_key_id = autocryptPeer.master_key_id AND ac_key.rank = 0) + LEFT JOIN keys AS gossip_key ON (gossip_key.master_key_id = gossip_master_key_id AND gossip_key.rank = 0) + WHERE package_name = ?1 AND identifier IN ?2; \ No newline at end of file diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq new file mode 100644 index 000000000..1553509aa --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq @@ -0,0 +1,13 @@ +-- TODO implement. this is only here for reference in SQLDelight +CREATE TABLE IF NOT EXISTS certs( + master_key_id INTEGER, + rank INTEGER, + key_id_certifier INTEGER, + type INTEGER, + verified INTEGER, + creation INTEGER, + data BLOB, + PRIMARY KEY(master_key_id, rank, key_id_certifier), + FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE + -- FOREIGN KEY(master_key_id, rank) REFERENCES user_packets(master_key_id, rank) ON DELETE CASCADE +); \ No newline at end of file diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index 14288f7bb..6101b4a02 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -246,7 +246,8 @@ public class InteropTest { KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application, LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application), LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application), - DatabaseNotifyManager.create(RuntimeEnvironment.application)) { + DatabaseNotifyManager.create(RuntimeEnvironment.application), + AutocryptPeerDao.getInstance(RuntimeEnvironment.application)) { @Override public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java index 2d045a7b9..6c0b9b4f2 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java @@ -19,12 +19,13 @@ import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowPackageManager; import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; @@ -63,7 +64,7 @@ public class KeychainExternalProviderTest { ContentResolver contentResolver = RuntimeEnvironment.application.getContentResolver(); ApiPermissionHelper apiPermissionHelper; ApiDataAccessObject apiDao; - AutocryptPeerDataAccessObject autocryptPeerDao; + AutocryptPeerDao autocryptPeerDao; @Before @@ -81,9 +82,9 @@ public class KeychainExternalProviderTest { apiDao = new ApiDataAccessObject(RuntimeEnvironment.application); apiPermissionHelper = new ApiPermissionHelper(RuntimeEnvironment.application, apiDao); - autocryptPeerDao = new AutocryptPeerDataAccessObject(RuntimeEnvironment.application, PACKAGE_NAME); + autocryptPeerDao = AutocryptPeerDao.getInstance(RuntimeEnvironment.application); - apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, PACKAGE_SIGNATURE)); + apiDao.insertApiApp(ApiApp.create(PACKAGE_NAME, PACKAGE_SIGNATURE)); } @Test(expected = AccessControlException.class) @@ -100,7 +101,7 @@ public class KeychainExternalProviderTest { @Test(expected = AccessControlException.class) public void testPermission__withWrongPackageCert() throws Exception { apiDao.deleteApiApp(PACKAGE_NAME); - apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, new byte[] { 1, 2, 4 })); + apiDao.insertApiApp(ApiApp.create(PACKAGE_NAME, new byte[] { 1, 2, 4 })); contentResolver.query( EmailStatus.CONTENT_URI, @@ -208,7 +209,8 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKey(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false); + autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date()); + autocryptPeerDao.updateKey(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { @@ -235,7 +237,8 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKey(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, true); + autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date()); + autocryptPeerDao.updateKey(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, true); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { @@ -262,7 +265,8 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKey("tid", new Date(), KEY_ID_PUBLIC, false); + autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date()); + autocryptPeerDao.updateKey(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, false); certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); Cursor cursor = contentResolver.query( @@ -306,8 +310,9 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKeyGossipFromAutocrypt("tid", new Date(), KEY_ID_PUBLIC); - autocryptPeerDao.delete("tid"); + autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date()); + autocryptPeerDao.updateKeyGossip(PACKAGE_NAME, "tid", new Date(), KEY_ID_PUBLIC, GossipOrigin.GOSSIP_HEADER); + autocryptPeerDao.deleteByIdentifier(PACKAGE_NAME, "tid"); Cursor cursor = contentResolver.query( AutocryptStatus.CONTENT_URI, new String[] { @@ -331,7 +336,8 @@ public class KeychainExternalProviderTest { insertSecretKeyringFrom("/test-keys/testring.sec"); insertPublicKeyringFrom("/test-keys/testring.pub"); - autocryptPeerDao.updateKeyGossipFromAutocrypt(AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC); + autocryptPeerDao.insertOrUpdateLastSeen(PACKAGE_NAME, "tid", new Date()); + autocryptPeerDao.updateKeyGossip(PACKAGE_NAME, AUTOCRYPT_PEER, new Date(), KEY_ID_PUBLIC, GossipOrigin.GOSSIP_HEADER); certifyKey(KEY_ID_SECRET, KEY_ID_PUBLIC, USER_ID_1); Cursor cursor = contentResolver.query( From de708288fb8b033338a9c3311fe326748407bcad Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 19 Jun 2018 11:28:19 +0200 Subject: [PATCH 013/124] minor layout fix with autocrypt ids --- .../sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java index f3bb61d51..5500b1674 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java @@ -248,6 +248,8 @@ public class IdentityAdapter extends RecyclerView.Adapter { } vIcon.setImageDrawable(info.getAppIcon()); + + vIcon.setVisibility(View.VISIBLE); vMore.setVisibility(View.VISIBLE); itemView.setClickable(info.getAutocryptPeerIntent() != null); From f3ef530b9641008f4d7c0b75f411dd9843632f0e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 19 Jun 2018 14:22:08 +0200 Subject: [PATCH 014/124] ditch ViewCertActivity and cert fragment in AdvKeyView --- OpenKeychain/src/main/AndroidManifest.xml | 9 - .../keychain/provider/KeychainContract.java | 11 - .../keychain/provider/KeychainProvider.java | 11 - .../keychain/ui/ViewCertActivity.java | 221 --------------- .../keychain/ui/ViewKeyAdvActivity.java | 8 +- .../keychain/ui/ViewKeyAdvCertsFragment.java | 129 --------- .../ui/adapter/CertSectionedListAdapter.java | 259 ------------------ .../ui/keyview/LinkedIdViewFragment.java | 3 +- .../presenter/IdentitiesPresenter.java | 10 +- .../main/res/layout/view_cert_activity.xml | 226 --------------- .../layout/view_key_adv_certs_fragment.xml | 71 ----- .../res/layout/view_key_adv_certs_header.xml | 17 -- .../res/layout/view_key_adv_certs_item.xml | 52 ---- 13 files changed, 4 insertions(+), 1023 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java delete mode 100644 OpenKeychain/src/main/res/layout/view_cert_activity.xml delete mode 100644 OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml delete mode 100644 OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml delete mode 100644 OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 6bea7ff0a..4bde8a2aa 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -168,15 +168,6 @@
- - - projectionMap = new HashMap<>(); projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); @@ -599,12 +594,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe qb.appendWhere(Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - if(match == KEY_RING_CERTS_SPECIFIC) { - qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.RANK + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(3)); - qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER+ " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(4)); - } if (match == KEY_RING_LINKED_ID_CERTS) { qb.appendWhere(" AND " + Tables.USER_PACKETS + "." diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java deleted file mode 100644 index d52a8f277..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - - -import java.util.Date; - -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.NavUtils; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v7.app.ActionBar; -import android.text.format.DateFormat; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.WrappedSignature; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import timber.log.Timber; - - -public class ViewCertActivity extends BaseActivity - implements LoaderManager.LoaderCallbacks { - - // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ - Certs.MASTER_KEY_ID, - Certs.USER_ID, - Certs.TYPE, - Certs.CREATION, - Certs.KEY_ID_CERTIFIER, - Certs.SIGNER_UID, - Certs.DATA, - }; - private static final int INDEX_MASTER_KEY_ID = 0; - private static final int INDEX_USER_ID = 1; - private static final int INDEX_TYPE = 2; - private static final int INDEX_CREATION = 3; - private static final int INDEX_KEY_ID_CERTIFIER = 4; - private static final int INDEX_SIGNER_UID = 5; - private static final int INDEX_DATA = 6; - - private Uri mDataUri; - - private long mCertifierKeyId; - - private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mReason, mCreation; - private TextView mCertifierKey, mCertifierUid, mStatus; - private View mRowReason; - private View mViewCertifierButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - - mSigneeKey = findViewById(R.id.signee_key); - mSigneeUid = findViewById(R.id.signee_uid); - mAlgorithm = findViewById(R.id.algorithm); - mType = findViewById(R.id.signature_type); - mReason = findViewById(R.id.reason); - mCreation = findViewById(R.id.creation); - - mCertifierKey = findViewById(R.id.signer_key_id); - mCertifierUid = findViewById(R.id.signer_uid); - - mRowReason = findViewById(R.id.row_reason); - - mViewCertifierButton = findViewById(R.id.view_cert_view_cert_key); - - mDataUri = getIntent().getData(); - if (mDataUri == null) { - Timber.e("Intent data missing. Should be Uri of key!"); - finish(); - return; - } - - getSupportLoaderManager().initLoader(0, null, this); - } - - @Override - protected void initLayout() { - setContentView(R.layout.view_cert_activity); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(this, mDataUri, PROJECTION, null, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - if (data.moveToFirst()) { - mSigneeKey.setText(KeyFormattingUtils.beautifyKeyId(data.getLong(INDEX_MASTER_KEY_ID))); - - String signeeUid = data.getString(INDEX_USER_ID); - mSigneeUid.setText(signeeUid); - - Date creationDate = new Date(data.getLong(INDEX_CREATION) * 1000); - mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(creationDate)); - - mCertifierKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER); - mCertifierKey.setText(KeyFormattingUtils.beautifyKeyId(mCertifierKeyId)); - - String certifierUid = data.getString(INDEX_SIGNER_UID); - if (certifierUid != null) { - mCertifierUid.setText(certifierUid); - } else { - mCertifierUid.setText(R.string.unknown_uid); - } - - WrappedSignature sig = WrappedSignature.fromBytes(data.getBlob(INDEX_DATA)); - - String algorithmStr = KeyFormattingUtils.getAlgorithmInfo(this, sig.getKeyAlgorithm(), null, null); - mAlgorithm.setText(algorithmStr); - - mRowReason.setVisibility(View.GONE); - switch (data.getInt(INDEX_TYPE)) { - case WrappedSignature.DEFAULT_CERTIFICATION: - mType.setText(R.string.cert_default); - break; - case WrappedSignature.NO_CERTIFICATION: - mType.setText(R.string.cert_none); - break; - case WrappedSignature.CASUAL_CERTIFICATION: - mType.setText(R.string.cert_casual); - break; - case WrappedSignature.POSITIVE_CERTIFICATION: - mType.setText(R.string.cert_positive); - break; - case WrappedSignature.CERTIFICATION_REVOCATION: { - mType.setText(R.string.cert_revoke); - if (sig.isRevocation()) { - try { - String reason = sig.getRevocationReason(); - if (reason != null) { - mReason.setText(reason); - } else { - mReason.setText(R.string.none); - } - } catch(PgpGeneralException e) { - mReason.setText(R.string.none); - } - } - mRowReason.setVisibility(View.VISIBLE); - break; - } - } - } - - // can't do this before the data is initialized - mViewCertifierButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent viewIntent = new Intent(ViewCertActivity.this, ViewKeyActivity.class); - - try { - KeyRepository keyRepository = - KeyRepository.create(ViewCertActivity.this); - long signerMasterKeyId = keyRepository.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mCertifierKeyId)).getMasterKeyId(); - viewIntent.setData(KeyRings.buildGenericKeyRingUri(signerMasterKeyId)); - startActivity(viewIntent); - } catch (PgpKeyNotFoundException e) { - // TODO notify user of this, maybe offer download? - Timber.e(e, "key not found!"); - } - } - }); - } - - @Override - public void onLoaderReset(Loader loader) { - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - Intent viewIntent = NavUtils.getParentActivityIntent(this); - viewIntent.setData(KeyRings.buildGenericKeyRingUri(mDataUri)); - NavUtils.navigateUpTo(this, viewIntent); - return true; - } - } - return super.onOptionsItemSelected(item); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index bc5233711..55fc4da39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -126,7 +126,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements mViewPager.setAdapter(mTabAdapter); // keep track which of these are action mode enabled! - mTabsWithActionMode = new boolean[5]; + mTabsWithActionMode = new boolean[4]; mTabAdapter.addTab(ViewKeyAdvStartFragment.class, null, getString(R.string.key_view_tab_start)); @@ -150,12 +150,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements keysBundle, getString(R.string.key_view_tab_keys)); mTabsWithActionMode[3] = true; - Bundle certsBundle = new Bundle(); - certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri); - mTabAdapter.addTab(ViewKeyAdvCertsFragment.class, - certsBundle, getString(R.string.key_view_tab_certs)); - mTabsWithActionMode[4] = false; - // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); mSlidingTabLayout.setOnPageChangeListener(this); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java deleted file mode 100644 index 1a11c1bce..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.view.View; -import android.view.ViewGroup; -import android.view.LayoutInflater; - -import com.tonicartos.superslim.LayoutManager; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.adapter.CertSectionedListAdapter; -import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; -import timber.log.Timber; - - -public class ViewKeyAdvCertsFragment extends RecyclerFragment - implements LoaderManager.LoaderCallbacks, CertSectionedListAdapter.CertListListener { - - public static final String ARG_DATA_URI = "data_uri"; - private Uri mDataUriCerts; - - /** - * Creates new instance of this fragment - */ - public static ViewKeyAdvCertsFragment newInstance(Uri dataUri) { - ViewKeyAdvCertsFragment frag = new ViewKeyAdvCertsFragment(); - - Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); - - frag.setArguments(args); - return frag; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.view_key_adv_certs_fragment, container, false); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - hideList(false); - - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); - getActivity().finish(); - return; - } else { - mDataUriCerts = KeychainContract.Certs.buildCertsUri(dataUri); - } - - CertSectionedListAdapter adapter = new CertSectionedListAdapter(getActivity(), null); - adapter.setCertListListener(this); - - setAdapter(adapter); - setLayoutManager(new LayoutManager(getActivity())); - - getLoaderManager().initLoader(0, null, this); - } - - public Loader onCreateLoader(int id, Bundle args) { - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), mDataUriCerts, - CertSectionedListAdapter.CertCursor.CERTS_PROJECTION, null, null, - CertSectionedListAdapter.CertCursor.CERTS_SORT_ORDER); - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Avoid NullPointerExceptions, if we get an empty result set. - if (data.getCount() == 0) { - return; - } - - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - getAdapter().swapCursor(CertSectionedListAdapter.CertCursor.wrap(data)); - - if (isResumed()) { - showList(true); - } else { - showList(false); - } - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - getAdapter().swapCursor(null); - } - - @Override - public void onClick(long masterKeyId, long signerKeyId, long rank) { - if(masterKeyId != 0L) { - Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class); - viewIntent.setData(KeychainContract.Certs.buildCertsSpecificUri( - masterKeyId, rank, signerKeyId)); - startActivity(viewIntent); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java deleted file mode 100644 index aa4551e74..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - - -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.WrappedSignature; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.ui.adapter.CertSectionedListAdapter.CertCursor; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; -import org.sufficientlysecure.keychain.ui.util.adapter.SectionCursorAdapter; - -import java.util.ArrayList; -import java.util.Arrays; - -public class CertSectionedListAdapter extends SectionCursorAdapter { - - private CertListListener mListener; - - public CertSectionedListAdapter(Context context, CertCursor cursor) { - super(context, CertCursor.wrap(cursor), 0); - } - - public void setCertListListener(CertListListener listener) { - mListener = listener; - } - - @Override - public long getIdFromCursor(CertCursor cursor) { - return cursor.getKeyId(); - } - - @Override - protected String getSectionFromCursor(CertCursor cursor) throws IllegalStateException { - return cursor.getRawSignerUserId(); - } - - @Override - protected CertSectionViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { - return new CertSectionViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.view_key_adv_certs_header, parent, false)); - } - - @Override - protected CertItemViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { - return new CertItemViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.view_key_adv_certs_item, parent, false)); - } - - @Override - protected void onBindSectionViewHolder(CertSectionViewHolder holder, String section) { - holder.bind(section); - } - - @Override - protected void onBindItemViewHolder(CertItemViewHolder holder, CertCursor cursor) { - holder.bind(cursor); - } - - class CertItemViewHolder extends SectionCursorAdapter.ViewHolder - implements View.OnClickListener { - - private TextView mSignerKeyId; - private TextView mSignerName; - private TextView mSignStatus; - - public CertItemViewHolder(View itemView) { - super(itemView); - - itemView.setClickable(true); - itemView.setOnClickListener(this); - - mSignerName = itemView.findViewById(R.id.signerName); - mSignStatus = itemView.findViewById(R.id.signStatus); - mSignerKeyId = itemView.findViewById(R.id.signerKeyId); - } - - public void bind(CertCursor cursor) { - String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix( - cursor.getCertifierKeyId()); - - OpenPgpUtils.UserId userId = cursor.getSignerUserId(); - if (userId.name != null) { - mSignerName.setText(userId.name); - } else { - mSignerName.setText(R.string.user_id_no_name); - } - - mSignerKeyId.setText(signerKeyId); - switch (cursor.getType()) { - case WrappedSignature.DEFAULT_CERTIFICATION: // 0x10 - mSignStatus.setText(R.string.cert_default); - break; - case WrappedSignature.NO_CERTIFICATION: // 0x11 - mSignStatus.setText(R.string.cert_none); - break; - case WrappedSignature.CASUAL_CERTIFICATION: // 0x12 - mSignStatus.setText(R.string.cert_casual); - break; - case WrappedSignature.POSITIVE_CERTIFICATION: // 0x13 - mSignStatus.setText(R.string.cert_positive); - break; - case WrappedSignature.CERTIFICATION_REVOCATION: // 0x30 - mSignStatus.setText(R.string.cert_revoke); - break; - } - } - - @Override - public void onClick(View v) { - if(mListener != null) { - int index = getCursorPositionWithoutSections(getAdapterPosition()); - if (moveCursor(index)) { - CertCursor cursor = getCursor(); - mListener.onClick( - cursor.getKeyId(), - cursor.getCertifierKeyId(), - cursor.getRank() - ); - } - } - } - } - - static class CertSectionViewHolder extends SectionCursorAdapter.ViewHolder { - private TextView mHeaderText; - - public CertSectionViewHolder(View itemView) { - super(itemView); - mHeaderText = itemView.findViewById(R.id.stickylist_header_text); - } - - public void bind(String text) { - mHeaderText.setText(text); - } - } - - public static class CertCursor extends CursorAdapter.SimpleCursor { - public static final String[] CERTS_PROJECTION; - static { - ArrayList projection = new ArrayList<>(); - projection.addAll(Arrays.asList(SimpleCursor.PROJECTION)); - projection.addAll(Arrays.asList( - KeychainContract.Certs.MASTER_KEY_ID, - KeychainContract.Certs.VERIFIED, - KeychainContract.Certs.TYPE, - KeychainContract.Certs.RANK, - KeychainContract.Certs.KEY_ID_CERTIFIER, - KeychainContract.Certs.USER_ID, - KeychainContract.Certs.SIGNER_UID - )); - - CERTS_PROJECTION = projection.toArray(new String[projection.size()]); - } - - public static final String CERTS_SORT_ORDER = - KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.RANK + " ASC, " - + KeychainContract.Certs.VERIFIED + " DESC, " - + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.TYPE + " DESC, " - + KeychainContract.Certs.SIGNER_UID + " ASC"; - - public static CertCursor wrap(Cursor cursor) { - if(cursor != null) { - return new CertCursor(cursor); - } else { - return null; - } - } - - private CertCursor(Cursor cursor) { - super(cursor); - } - - public long getKeyId() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID); - return getLong(index); - } - - public boolean isVerified() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); - return getInt(index) > 0; - } - - public int getType() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.TYPE); - return getInt(index); - } - - public long getRank() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.RANK); - return getLong(index); - } - - public long getCertifierKeyId() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); - return getLong(index); - } - - public String getRawUserId() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.USER_ID); - return getString(index); - } - - public String getRawSignerUserId() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID); - return getString(index); - } - - public String getName() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.NAME); - return getString(index); - } - - public String getEmail() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.EMAIL); - return getString(index); - } - - public String getComment() { - int index = getColumnIndexOrThrow(KeychainContract.Certs.COMMENT); - return getString(index); - } - - public OpenPgpUtils.UserId getSignerUserId() { - return KeyRing.splitUserId(getRawSignerUserId()); - } - } - - public interface CertListListener { - void onClick(long masterKeyId, long signerKeyId, long rank); - } -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index cfa9b283f..c841a4ee7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -99,8 +99,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements private int lidRank; private long certifyKeyId; - public static LinkedIdViewFragment newInstance(Uri dataUri, int rank, - boolean isSecret, long masterKeyId) throws IOException { + public static LinkedIdViewFragment newInstance(Uri dataUri, int rank, boolean isSecret, long masterKeyId) { LinkedIdViewFragment frag = new LinkedIdViewFragment(); Bundle args = new Bundle(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java index 478d0be5f..566868d34 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java @@ -114,14 +114,8 @@ public class IdentitiesPresenter implements Observer> { } private void showLinkedId(final LinkedIdInfo info) { - final LinkedIdViewFragment frag; - try { - Uri dataUri = UserPackets.buildLinkedIdsUri(KeyRings.buildGenericKeyRingUri(masterKeyId)); - frag = LinkedIdViewFragment.newInstance(dataUri, info.getRank(), isSecret, masterKeyId); - } catch (IOException e) { - Timber.e(e, "IOException"); - return; - } + Uri dataUri = UserPackets.buildLinkedIdsUri(KeyRings.buildGenericKeyRingUri(masterKeyId)); + LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(dataUri, info.getRank(), isSecret, masterKeyId); viewKeyMvpView.switchToFragment(frag, "linked_id"); } diff --git a/OpenKeychain/src/main/res/layout/view_cert_activity.xml b/OpenKeychain/src/main/res/layout/view_cert_activity.xml deleted file mode 100644 index f412dbe92..000000000 --- a/OpenKeychain/src/main/res/layout/view_cert_activity.xml +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml deleted file mode 100644 index 80bb21e16..000000000 --- a/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml b/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml deleted file mode 100644 index 3ebb0f855..000000000 --- a/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml b/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml deleted file mode 100644 index f1a59a4f9..000000000 --- a/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - From 6585e7113d2424e9df45b3ec71c7005b182cbd97 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 19 Jun 2018 15:11:04 +0200 Subject: [PATCH 015/124] move loading of certs into CertificationDao --- .../keychain/livedata/CertificationDao.java | 41 ++++++++++ .../keychain/livedata/GenericLiveData.java | 26 +++++++ .../keychain/model/Certification.java | 20 +++++ .../keychain/provider/KeychainContract.java | 7 -- .../keychain/provider/KeychainProvider.java | 54 ------------- .../ui/keyview/LinkedIdViewFragment.java | 19 +++-- .../keychain/ui/widget/CertListWidget.java | 76 ++----------------- .../org/sufficientlysecure/keychain/Certs.sq | 23 ++++-- 8 files changed, 120 insertions(+), 146 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java new file mode 100644 index 000000000..e6236bd41 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java @@ -0,0 +1,41 @@ +package org.sufficientlysecure.keychain.livedata; + + +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.content.Context; +import android.database.Cursor; + +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.model.Certification; +import org.sufficientlysecure.keychain.model.Certification.CertDetails; +import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; + + +public class CertificationDao { + private final SupportSQLiteDatabase db; + private final DatabaseNotifyManager databaseNotifyManager; + + public static CertificationDao getInstance(Context context) { + KeychainDatabase keychainDatabase = new KeychainDatabase(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); + + return new CertificationDao(keychainDatabase.getWritableDatabase(), databaseNotifyManager); + } + + private CertificationDao(SupportSQLiteDatabase writableDatabase, DatabaseNotifyManager databaseNotifyManager) { + this.db = writableDatabase; + this.databaseNotifyManager = databaseNotifyManager; + } + + public CertDetails getVerifyingCertDetails(long masterKeyId, int userPacketRank) { + SqlDelightQuery query = Certification.FACTORY.selectVerifyingCertDetails(masterKeyId, userPacketRank); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToFirst()) { + return Certification.CERT_DETAILS_MAPPER.map(cursor); + } + } + return null; + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java new file mode 100644 index 000000000..f46c731db --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java @@ -0,0 +1,26 @@ +package org.sufficientlysecure.keychain.livedata; + + +import android.content.Context; +import android.net.Uri; + +import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; + + +public class GenericLiveData extends AsyncTaskLiveData { + private GenericDataLoader genericDataLoader; + + public GenericLiveData(Context context, Uri uri, GenericDataLoader genericDataLoader) { + super(context, uri); + this.genericDataLoader = genericDataLoader; + } + + @Override + protected T asyncLoadData() { + return genericDataLoader.loadData(); + } + + public interface GenericDataLoader { + T loadData(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java new file mode 100644 index 000000000..b9c72596e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.CertsModel; + + +@AutoValue +public abstract class Certification implements CertsModel { + public static final CertsModel.Factory FACTORY = + new CertsModel.Factory<>(AutoValue_Certification::new); + + public static final SelectVerifyingCertDetailsMapper CERT_DETAILS_MAPPER = + new SelectVerifyingCertDetailsMapper<>(AutoValue_Certification_CertDetails::new); + + @AutoValue + public static abstract class CertDetails implements CertsModel.SelectVerifyingCertDetailsModel { + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index dbfede4aa..19641d835 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -294,13 +294,6 @@ public class KeychainContract { public static Uri buildCertsUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_CERTS).build(); } - - public static Uri buildLinkedIdCertsUri(Uri uri, int rank) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)) - .appendPath(PATH_LINKED_IDS).appendPath(Integer.toString(rank)) - .appendPath(PATH_CERTS).build(); - } - } private KeychainContract() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 53cc79aad..f3b38f8b1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -66,7 +66,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RING_PUBLIC = 203; private static final int KEY_RING_CERTS = 205; private static final int KEY_RING_LINKED_IDS = 207; - private static final int KEY_RING_LINKED_ID_CERTS = 208; private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; @@ -154,10 +153,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_LINKED_IDS, KEY_RING_LINKED_IDS); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_LINKED_IDS + "/*/" - + KeychainContract.PATH_CERTS, - KEY_RING_LINKED_ID_CERTS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_PUBLIC, KEY_RING_PUBLIC); @@ -560,55 +555,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case KEY_RING_CERTS: - case KEY_RING_LINKED_ID_CERTS: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); - projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID); - projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); - projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); - projectionMap.put(Certs.TYPE, Tables.CERTS + "." + Certs.TYPE); - projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); - projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); - projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA); - projectionMap.put(Certs.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); - projectionMap.put(Certs.SIGNER_UID, "signer." + UserPackets.USER_ID + " AS " + Certs.SIGNER_UID); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.CERTS - + " JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + " AND " - + Tables.CERTS + "." + Certs.RANK + " = " - + Tables.USER_PACKETS + "." + UserPackets.RANK - + ") LEFT JOIN " + Tables.USER_PACKETS + " AS signer ON (" - + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " - + "signer." + UserPackets.MASTER_KEY_ID - + " AND " - + "signer." + Keys.RANK + " = 0" - + ")"); - - groupBy = Tables.CERTS + "." + Certs.RANK + ", " - + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER; - - qb.appendWhere(Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - - if (match == KEY_RING_LINKED_ID_CERTS) { - qb.appendWhere(" AND " + Tables.USER_PACKETS + "." - + UserPackets.TYPE + " IS NOT NULL"); - - qb.appendWhere(" AND " + Tables.USER_PACKETS + "." - + UserPackets.RANK + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(3)); - } else { - qb.appendWhere(" AND " + Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); - } - - break; - } - default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index c841a4ee7..bf5c0f997 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui.keyview; import java.io.IOException; import java.util.Collections; +import android.arch.lifecycle.LiveData; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -54,6 +55,9 @@ import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.LinkedResource; import org.sufficientlysecure.keychain.linked.LinkedTokenResource; import org.sufficientlysecure.keychain.linked.UriAttribute; +import org.sufficientlysecure.keychain.livedata.CertificationDao; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.Certification.CertDetails; import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -407,17 +411,18 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource()); viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying()); - { - Bundle args = new Bundle(); - args.putParcelable(CertListWidget.ARG_URI, Certs.buildLinkedIdCertsUri(dataUri, lidRank)); - args.putBoolean(CertListWidget.ARG_IS_SECRET, isSecret); - getLoaderManager().initLoader(CertListWidget.LOADER_ID_LINKED_CERTS, - args, viewHolder.vLinkedCerts); - } + CertificationDao certificationDao = CertificationDao.getInstance(getContext()); + LiveData certDetailsLiveData = new GenericLiveData<>( + getContext(), null, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank)); + certDetailsLiveData.observe(this, this::onLoadCertDetails); return root; } + private void onLoadCertDetails(CertDetails certDetails) { + viewHolder.vLinkedCerts.setData(certDetails, isSecret); + } + void verifyResource() { // only one at a time (no sync needed, taskInProgress is only touched in ui thread) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java index 7606f4ba6..f84c51367 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertListWidget.java @@ -33,43 +33,14 @@ import android.widget.TextView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.Certification.CertDetails; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -public class CertListWidget extends ViewAnimator - implements LoaderManager.LoaderCallbacks { - - public static final int LOADER_ID_LINKED_CERTS = 38572; - - public static final String ARG_URI = "uri"; - public static final String ARG_IS_SECRET = "is_secret"; - - - // These are the rows that we will retrieve. - static final String[] CERTS_PROJECTION = new String[]{ - KeychainContract.Certs._ID, - KeychainContract.Certs.MASTER_KEY_ID, - KeychainContract.Certs.VERIFIED, - KeychainContract.Certs.TYPE, - KeychainContract.Certs.RANK, - KeychainContract.Certs.KEY_ID_CERTIFIER, - KeychainContract.Certs.USER_ID, - KeychainContract.Certs.SIGNER_UID, - KeychainContract.Certs.CREATION - }; - public static final int INDEX_MASTER_KEY_ID = 1; - public static final int INDEX_VERIFIED = 2; - public static final int INDEX_TYPE = 3; - public static final int INDEX_RANK = 4; - public static final int INDEX_KEY_ID_CERTIFIER = 5; - public static final int INDEX_USER_ID = 6; - public static final int INDEX_SIGNER_UID = 7; - public static final int INDEX_CREATION = 8; - +public class CertListWidget extends ViewAnimator { private TextView vCollapsed; private ListView vExpanded; private View vExpandButton; - private boolean mIsSecret; public CertListWidget(Context context, AttributeSet attrs) { super(context, attrs); @@ -105,41 +76,11 @@ public class CertListWidget extends ViewAnimator setDisplayedChild(expanded ? 1 : 0); } - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri uri = args.getParcelable(ARG_URI); - mIsSecret = args.getBoolean(ARG_IS_SECRET, false); - return new CursorLoader(getContext(), uri, - CERTS_PROJECTION, null, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - - if (data == null || !data.moveToFirst()) { - return; - } - - // TODO support external certificates - Long certTime = null; - while (!data.isAfterLast()) { - - int verified = data.getInt(INDEX_VERIFIED); - long creation = data.getLong(INDEX_CREATION) * 1000; - - if (verified == Certs.VERIFIED_SECRET) { - if (certTime == null || certTime > creation) { - certTime = creation; - } - } - - data.moveToNext(); - } - - if (certTime != null) { + public void setData(CertDetails certDetails, boolean isSecret) { + if (certDetails != null) { CharSequence relativeTimeStr = DateUtils - .getRelativeTimeSpanString(certTime, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_ALL); - if (mIsSecret) { + .getRelativeTimeSpanString(certDetails.creation(), System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_ALL); + if (isSecret) { vCollapsed.setText("You created this identity " + relativeTimeStr + "."); } else { vCollapsed.setText("You verified and confirmed this identity " + relativeTimeStr + "."); @@ -150,9 +91,4 @@ public class CertListWidget extends ViewAnimator } - @Override - public void onLoaderReset(Loader loader) { - setVisibility(View.GONE); - } - } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq index 1553509aa..c80812b10 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq @@ -1,13 +1,20 @@ -- TODO implement. this is only here for reference in SQLDelight CREATE TABLE IF NOT EXISTS certs( - master_key_id INTEGER, - rank INTEGER, - key_id_certifier INTEGER, - type INTEGER, - verified INTEGER, - creation INTEGER, - data BLOB, + master_key_id INTEGER NOT NULL, + rank INTEGER NOT NULL, + key_id_certifier INTEGER NOT NULL, + type INTEGER NOT NULL, + verified INTEGER NOT NULL, + creation INTEGER NOT NULL, + data BLOB NOT NULL, PRIMARY KEY(master_key_id, rank, key_id_certifier), FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE -- FOREIGN KEY(master_key_id, rank) REFERENCES user_packets(master_key_id, rank) ON DELETE CASCADE -); \ No newline at end of file +); + +selectVerifyingCertDetails: +SELECT master_key_id AS masterKeyId, key_id_certifier AS signerMasterKeyId, creation * 1000 AS creation + FROM certs + WHERE verified = 1 AND master_key_id = ? AND rank = ? + ORDER BY creation DESC + LIMIT 1; \ No newline at end of file From 77c89cfa9869b487ef121d6a70098984d446c1b6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 19 Jun 2018 15:33:41 +0200 Subject: [PATCH 016/124] extract reading of public key data from ContentProvider --- .../keychain/model/KeyRingPublic.java | 12 +++++ .../keychain/provider/KeyRepository.java | 52 ++++++++++--------- .../provider/KeyWritableRepository.java | 15 +++--- .../keychain/provider/KeychainContract.java | 13 ----- .../keychain/provider/KeychainProvider.java | 31 ----------- .../keychain/ui/SafeSlingerActivity.java | 14 +++-- .../keychain/KeyRingsPublic.sq | 11 ++-- 7 files changed, 62 insertions(+), 86 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java new file mode 100644 index 000000000..65a7cf6dd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeyRingPublic.java @@ -0,0 +1,12 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.KeyRingsPublicModel; + +@AutoValue +public abstract class KeyRingPublic implements KeyRingsPublicModel { + public static final Factory FACTORY = new Factory<>(AutoValue_KeyRingPublic::new); + + public static final Mapper MAPPER = new Mapper<>(FACTORY); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 5b5f6e82a..5db3f9cd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -23,12 +23,15 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; @@ -36,7 +39,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import timber.log.Timber; @@ -54,6 +56,8 @@ public class KeyRepository { final ContentResolver contentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; final LocalSecretKeyStorage localSecretKeyStorage; + final SupportSQLiteDatabase db; + OperationLog mLog; int mIndent; @@ -61,18 +65,23 @@ public class KeyRepository { ContentResolver contentResolver = context.getContentResolver(); LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); + SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase(); - return new KeyRepository(contentResolver, localPublicKeyStorage, localSecretKeyStorage); + return new KeyRepository(contentResolver, db, localPublicKeyStorage, localSecretKeyStorage); } - private KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage, + private KeyRepository(ContentResolver contentResolver, SupportSQLiteDatabase db, + LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage) { - this(contentResolver, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0); + this(contentResolver, db, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0); } - KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage, - LocalSecretKeyStorage localSecretKeyStorage, OperationLog log, int indent) { + KeyRepository(ContentResolver contentResolver, SupportSQLiteDatabase db, + LocalPublicKeyStorage localPublicKeyStorage, + LocalSecretKeyStorage localSecretKeyStorage, + OperationLog log, int indent) { this.contentResolver = contentResolver; + this.db = db; mLocalPublicKeyStorage = localPublicKeyStorage; this.localSecretKeyStorage = localSecretKeyStorage; mIndent = indent; @@ -107,10 +116,6 @@ public class KeyRepository { return result; } - Object getGenericDataOrNull(Uri uri, String column, int type) throws NotFoundException { - return getGenericData(uri, new String[]{column}, new int[]{type}, null).get(column); - } - Object getGenericData(Uri uri, String column, int type, String selection) throws NotFoundException { return getGenericData(uri, new String[]{column}, new int[]{type}, selection).get(column); @@ -282,23 +287,20 @@ public class KeyRepository { } public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException { - byte[] data = (byte[]) getGenericDataOrNull(KeyRingData.buildPublicKeyRingUri(masterKeyId), - KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB); - - if (data == null) { - try { - data = mLocalPublicKeyStorage.readPublicKey(masterKeyId); - } catch (IOException e) { - Timber.e(e, "Error reading public key from storage!"); - throw new NotFoundException(); + SqlDelightQuery query = KeyRingPublic.FACTORY.selectByMasterKeyId(masterKeyId); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToFirst()) { + KeyRingPublic keyRingPublic = KeyRingPublic.MAPPER.map(cursor); + byte[] keyRingData = keyRingPublic.key_ring_data(); + if (keyRingData == null) { + keyRingData = mLocalPublicKeyStorage.readPublicKey(masterKeyId); + } + return keyRingData; } + } catch (IOException e) { + Timber.e(e, "Error reading public key from storage!"); } - - if (data == null) { - throw new NotFoundException(); - } - - return data; + throw new NotFoundException(); } public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index a4eed8d49..f860a53a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Context; @@ -90,23 +91,25 @@ public class KeyWritableRepository extends KeyRepository { LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); - return new KeyWritableRepository(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, - autocryptPeerDao); + SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase(); + return new KeyWritableRepository(context, db, + localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, autocryptPeerDao); } @VisibleForTesting KeyWritableRepository(Context context, - LocalPublicKeyStorage localPublicKeyStorage, + SupportSQLiteDatabase db, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, AutocryptPeerDao autocryptPeerDao) { - this(context, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0, + this(context, db, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0, autocryptPeerDao); } - private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, + private KeyWritableRepository(Context context, SupportSQLiteDatabase db, + LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, OperationLog log, int indent, AutocryptPeerDao autocryptPeerDao) { - super(context.getContentResolver(), localPublicKeyStorage, localSecretKeyStorage, log, indent); + super(context.getContentResolver(), db, localPublicKeyStorage, localSecretKeyStorage, log, indent); this.context = context; this.databaseNotifyManager = databaseNotifyManager; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 19641d835..85e79df25 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -186,22 +186,9 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_ring_data"; - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.key_ring_data"; - - public static Uri buildPublicKeyRingUri() { - return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build(); - } - public static Uri buildPublicKeyRingUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_PUBLIC).build(); } - - public static Uri buildPublicKeyRingUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build(); - } } public static class Keys implements KeysColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index f3b38f8b1..708bfd649 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -57,7 +57,6 @@ import static android.database.DatabaseUtils.dumpCursorToString; public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface { private static final int KEY_RINGS_UNIFIED = 101; - private static final int KEY_RINGS_PUBLIC = 102; private static final int KEY_RINGS_USER_IDS = 104; private static final int KEY_RING_UNIFIED = 200; @@ -90,17 +89,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe * *
          * key_rings/unified
-         * key_rings/public
-         * key_rings/secret
          * key_rings/user_ids
          * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_UNIFIED, KEY_RINGS_UNIFIED); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_PUBLIC, - KEY_RINGS_PUBLIC); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_USER_IDS, KEY_RINGS_USER_IDS); @@ -133,12 +127,8 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe * key_rings/_/keys * key_rings/_/user_ids * key_rings/_/linked_ids - * key_rings/_/linked_ids/_ - * key_rings/_/linked_ids/_/certs * key_rings/_/public - * key_rings/_/secret * key_rings/_/certs - * key_rings/_/certs/_/_ * */ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" @@ -190,9 +180,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public String getType(@NonNull Uri uri) { final int match = mUriMatcher.match(uri); switch (match) { - case KEY_RING_PUBLIC: - return KeyRings.CONTENT_ITEM_TYPE; - case KEY_RING_KEYS: return Keys.CONTENT_TYPE; @@ -537,24 +524,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case KEY_RINGS_PUBLIC: - case KEY_RING_PUBLIC: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id"); - projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); - projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.KEY_RINGS_PUBLIC); - - if(match == KEY_RING_PUBLIC) { - qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - } - - break; - } - default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index 5da61584f..c9c8c80a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import android.annotation.TargetApi; import android.content.Intent; import android.graphics.PorterDuff; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.View; @@ -40,9 +39,8 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -67,12 +65,14 @@ public class SafeSlingerActivity extends BaseActivity private ArrayList mKeyList; private HkpKeyserverAddress mKeyserver; private CryptoOperationHelper mOperationHelper; + private KeyRepository keyRepository; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + keyRepository = KeyRepository.create(this); mMasterKeyId = getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0); NumberPicker picker = findViewById(R.id.safe_slinger_picker); @@ -104,10 +104,8 @@ public class SafeSlingerActivity extends BaseActivity } private void startExchange(long masterKeyId, int number) { - // retrieve public key blob and start SafeSlinger - Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(masterKeyId); try { - byte[] keyBlob = KeyRepository.create(this).getCachedPublicKeyRing(uri).getEncoded(); + byte[] keyBlob = keyRepository.loadPublicKeyRingData(masterKeyId); Intent slingerIntent = new Intent(this, ExchangeActivity.class); @@ -115,8 +113,8 @@ public class SafeSlingerActivity extends BaseActivity slingerIntent.putExtra(ExchangeConfig.extra.USER_DATA, keyBlob); slingerIntent.putExtra(ExchangeConfig.extra.HOST_NAME, Constants.SAFESLINGER_SERVER); startActivityForResult(slingerIntent, REQUEST_CODE_SAFE_SLINGER); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "personal key not found"); + } catch (NotFoundException e) { + Timber.e(e, "key for transfer not found"); } } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq index be8435d7b..67c48f36f 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq @@ -1,4 +1,9 @@ CREATE TABLE IF NOT EXISTS keyrings_public ( - master_key_id INTEGER PRIMARY KEY, - key_ring_data BLOB -); \ No newline at end of file + master_key_id INTEGER NOT NULL PRIMARY KEY, + key_ring_data BLOB NULL +); + +selectByMasterKeyId: +SELECT * + FROM keyrings_public + WHERE master_key_id = ?; \ No newline at end of file From 72f3ed89a6b5c47d775340279d853269264eac2e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 20 Jun 2018 00:39:08 +0200 Subject: [PATCH 017/124] update build tools to 27.1.1, and gradle wrapper to 4.8 --- OpenKeychain/build.gradle | 66 ++++++++++++------------ build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index dca61bd27..0163bc22e 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -9,12 +9,12 @@ dependencies { // NOTE: libraries are pinned to a specific build, see below // from local Android SDK - compile 'com.android.support:support-v4:27.0.2' - compile 'com.android.support:appcompat-v7:27.0.2' - compile 'com.android.support:design:27.0.2' - compile 'com.android.support:recyclerview-v7:27.0.2' - compile 'com.android.support:cardview-v7:27.0.2' - compile 'com.android.support:support-annotations:27.0.2' + compile 'com.android.support:support-v4:27.1.1' + compile 'com.android.support:appcompat-v7:27.1.1' + compile 'com.android.support:design:27.1.1' + compile 'com.android.support:recyclerview-v7:27.1.1' + compile 'com.android.support:cardview-v7:27.1.1' + compile 'com.android.support:support-annotations:27.1.1' // JCenter etc. compile 'com.journeyapps:zxing-android-embedded:3.4.0' @@ -76,9 +76,9 @@ dependencies { // UI testing with Espresso // Force usage of support libs in the test app, since they are internally used by the runner module. // https://github.com/googlesamples/android-testing/blob/master/ui/espresso/BasicSample/app/build.gradle#L28 - androidTestCompile 'com.android.support:support-annotations:27.0.2' - androidTestCompile 'com.android.support:appcompat-v7:27.0.2' - androidTestCompile 'com.android.support:design:27.0.2' + androidTestCompile 'com.android.support:support-annotations:27.1.1' + androidTestCompile 'com.android.support:appcompat-v7:27.1.1' + androidTestCompile 'com.android.support:design:27.1.1' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' @@ -107,30 +107,36 @@ dependencies { // Comment out the libs referenced as git submodules! dependencyVerification { verify = [ - 'com.android.support:design:fa5c27a705310e95a8f4099c98777132ed901a0d69178942306bb34cd76f0d57', + 'com.android.support:design:7225973f7ee03765008a9c2f17a40b154c6885169fef022276e811c926a2202c', 'com.journeyapps:zxing-android-embedded:2422d83c2c09a7b645f516c8458ececba6a7da47b94e40778d876facf495c660', 'org.sufficientlysecure:donations:2be4183afa5e35263e37346344cfea48681f3c987e6832dd4acde227c13ccad6', - 'com.android.support:support-v4:1b2b37169fcccfef5e563d273749e3792decdce9818bc17932403a2363f537b4', - 'com.futuremind.recyclerfastscroll:fastscroll:ae655201885a9dbb5fabecb4adfefbb23ffdbca26a2b4ea255ec1bf6f214c606', + 'com.android.support:support-v4:4f41dfc3e89f2738e45c86264a85c0934d055ee8ebe2020e23c97f303b80a48b', 'com.mikepenz:fastadapter:21d4ecb5c128bcda37b14e7998d799ed52cfc768b72cdf3d5578bb6775769ebd', 'com.mikepenz:materialize:942ccf5e2aa1a46803aa884e8dc7bbaf2a9e8e9996a0cf92e3fe2f44a8592ba4', - 'com.android.support:appcompat-v7:b2825e8b47f665d3362d8481c8d147d1af9230d16f23a2b94f6ccbc53c68cec1', + 'com.android.support:appcompat-v7:0c7808fbbc5838d831e32e3c0a6f84e1f2c981deb8f11e010650f2b57923a335', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', - 'com.tonicartos:superslim:ca89b5c674660cc6918a8f8fd385065bffeee27983e0d33c7c2f0ad7b34d2d49', - 'com.android.support:recyclerview-v7:3eb953930f10941f2b0447ec123a9b03d2746a42a99c523e82c3ece3308ca70b', - 'com.android.support:cardview-v7:57f867a3c8f33e2d4dc0a03e2dfa03cad6267a908179f04a725a68ea9f0b8ccf', + 'com.android.support:recyclerview-v7:d735e4727878e99ef3980c10d15dc3468462fd509d4fb60cb8bd20b0f735085c', + 'com.android.support:cardview-v7:8ed955dd037d82a7b4bbcaedb4f896523c3e4c1bf3ca698ce807c350767a2886', 'org.sufficientlysecure:html-textview:ed740adf05cae2373999c7a3047c803183d9807b2cf66162902090d7c112a832', 'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240', - 'com.android.support:transition:1a7db0453c1467fc8fd815e6d50ca6bb475a7a9ba6b5f3b307329688a7c62a68', - 'com.android.support:support-media-compat:6dd9327ee9aa467cab479aad97df375072b2b6ba61eadffdaa5a88de3843c457', - 'com.android.support:support-fragment:e4358388022a2205777575a7251fe357334658e4123d5d6e3b082f5899d9b011', - 'com.android.support:support-core-utils:b69c6e1e7731b876b910fc7100bcadf40a57f27b32ca26b91400995542112c96', + 'android.arch.persistence:db-framework:e8310c66979f8823cfe583951abfde96824b176289ba77b750a25be00d25981a', + 'com.android.support:support-media-compat:55e9837dda88b74a8c812c63a78c63fd83c6c039a8c22d318492663a493585eb', 'com.jpardogo.materialtabstrip:library:4ee2f1211c302b45fb8c627cc5b240dc6b38b7aaaab1b8bffc81663e1b108013', - 'com.android.support:animated-vector-drawable:5b117a2c13a898c2a3c84c480d64edcfac2ef720aa9b742c29249fac774ffc48', - 'com.android.support:support-core-ui:2284072511a95d504c074de80c82cd33724c6d2754117833b98ba3a09994163e', - 'com.android.support:support-vector-drawable:bf4f4fcbf58b1380616581224e6487c230bfdb3434ec353d4adaa4b1f4865cfa', - 'com.android.support:support-compat:ed4d25d91a0b13d8b9def1c0de69ed03d7fb89d50fb37eb0e9b63b0cf7a42357', - 'com.android.support:support-annotations:af05330d997eb92a066534dbe0a3ea24347d26d7001221092113ae02a8f233da', + 'android.arch.lifecycle:extensions:851f718fd2afda1e7aa93537dae1a5c1fe47710db62dcd7cd24c4b3b14ef0d90', + 'com.android.support:support-fragment:ec72d6ac36a1a0e6523bbddba33d73ffad070b9b3dd246cc44d8727a41ddb5e6', + 'com.android.support:animated-vector-drawable:59670473f6e98fda792f7bef25dd7292b0a3106031c7a5e30eb020bf26f077bd', + 'com.android.support:support-core-ui:a3ae20e6d5dffba69ac97b99846d2738003af8563843d5f3c9dc4c35b4804241', + 'com.android.support:support-core-utils:61036832c54e8701aae954fc3bf96d1d80bf8d9dd531bff77d72def456ba087a', + 'com.android.support:support-vector-drawable:1c0f421114cf4627cf208776d6eb4f76340c78b7e96fe6e12b3e6eb950caf1b9', + 'com.android.support:transition:c0765b2f3c78696567ec5b3f519d22da1e3df11ac994625adf4bb4dc571caacc', + 'com.android.support:support-compat:880ce01ff5be42b233ff8ec0c61cefb7dc3dc9500fea9e24423214813ac27ea2', + 'android.arch.persistence:db:7c0a51d5fc890a8fb94a3370ff599243ec3485cca63daba3cc2bb197835dc521', + 'android.arch.lifecycle:runtime:094fd793924dd6a5136753e599ac8174a8147f4a401386b694ba7d818c223e2e', + 'android.arch.lifecycle:livedata-core:14e57ff8ffb65a80c7e72d91f2076acccdaf2970f234c6261e03a6127eb5206b', + 'android.arch.lifecycle:common:614e31cfd33255dc4d5f5d8e62cfa6be2fbbc2a35643a79dc3ed008004c30807', + 'android.arch.core:runtime:83400f7575bcfb8a2eeec64e05590f037bfaed1e56aa3a4214d20e55878445e3', + 'android.arch.core:common:d34824b794bc92ff8f647a9bb13a7c73de920de5b47075b5d2c4f0770e9b8bfd', + 'com.android.support:support-annotations:3365960206c3d2b09e845f555e7f88f8effc8d2f00b369e66c4be384029299cf', 'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d', 'org.commonjava.googlecode.markdown4j:markdown4j:28eb991f702c6d85d6cafd68c24d1ce841d1f5c995c943f25aedb433c0c13f60', 'com.squareup.okhttp3:okhttp-urlconnection:16a410e5c4457ab381759486df6f840fdc7cc426d67433d4da1b7d65ed2b3b33', @@ -145,18 +151,12 @@ dependencyVerification { 'com.mikepenz:fontawesome-typeface:ee47b7fe97b90412f01f2fcdd78f65a4edb0ab00006f5ef59ed00516baca9309', 'com.mikepenz:community-material-typeface:d6035d261c5eba880cd7fe5dcb8cc00b09bfe6d41063b881b759e9897dc7b7c9', 'com.fidesmo:nordpol-android:9a992eca347ff7af6e99ff48078954b44b26f26fdc5463139e340234757a24f7', + 'com.jakewharton.timber:timber:d553d3d3e883ce7d061f1b21b95d6ee0840f3bfbf6d3bd51c5671f0b0f0b0091', 'org.glassfish:javax.annotation:339c876b928766329cc0657920366e75beb25f932b80bb3b26df6c0e687a9582', 'com.ryanharter.auto.value:auto-value-parcel-adapter:f730534497f7de81f62f1165df65e750522fdaedabd56031ee1c2d9da2544e17', 'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850', 'com.fidesmo:nordpol-core:296e71b12884a9cd28cf00ab908973bbf776a90be1f23ac897380d91604e614d', - 'com.jakewharton.timber:timber:d553d3d3e883ce7d061f1b21b95d6ee0840f3bfbf6d3bd51c5671f0b0f0b0091', - 'android.arch.lifecycle:runtime:d0b36278878c82b838acc4308595bec61a3b5f6e7f2acc34172d7e071b2cf26d', - 'android.arch.lifecycle:common:ff0215b54e7cbaaa898f8fd00e265ed6ea198859e10604bc1c5e78477df48b5c', - 'android.arch.core:common:5192934cd73df32e2c15722ed7fc488dde90baaec9ae030010dd1a80fb4e74e1', - 'android.arch.lifecycle:runtime:d0b36278878c82b838acc4308595bec61a3b5f6e7f2acc34172d7e071b2cf26d', - 'android.arch.core:runtime:9e08fc5c4d6e48f58c6865b55ba0e72a88f907009407767274187a873e524734', - 'android.arch.core:common:5192934cd73df32e2c15722ed7fc488dde90baaec9ae030010dd1a80fb4e74e1', - 'android.arch.lifecycle:common:ff0215b54e7cbaaa898f8fd00e265ed6ea198859e10604bc1c5e78477df48b5c', + 'android.arch.lifecycle:viewmodel:6407c93a5ea9850661dca42a0068d6f3deccefd7228ee69bae1c35d70cbc2557', ] } diff --git a/build.gradle b/build.gradle index 639d96fc5..d4a5da611 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { // NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.3' classpath files('gradle-witness.jar') // bintray dependency to satisfy dependency of openpgp-api lib classpath 'com.novoda:bintray-release:0.8.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3482ab125..98d7a1155 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip -distributionSha256Sum=dd9b24950dc4fca7d1ca5f1ccd57ca8c5b9eb407e3e6e0f48174fde4bb19ed06 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip +distributionSha256Sum=da9600da2a28a43f5f77364deecbb9b01c1ddb7d3ecafe1d5c93bcd8a8059ab1 From f87209d24266b7044b4f9bbf00d19c7d47d7f2af Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 20 Jun 2018 00:45:34 +0200 Subject: [PATCH 018/124] use FlexibleAdapter with LiveData in KeyListFragment --- OpenKeychain/build.gradle | 7 +- .../keychain/matcher/CustomMatchers.java | 7 +- .../NoScrollableSwipeRefreshLayout.java | 473 ------------ .../keychain/livedata/KeyRingDao.java | 41 ++ .../keychain/model/AutocryptPeer.java | 8 +- .../keychain/model/Key.java | 50 ++ .../keychain/provider/KeychainDatabase.java | 30 +- .../keychain/ui/KeyListFragment.java | 344 +++++---- .../keychain/ui/LogDisplayFragment.java | 10 +- .../ui/adapter/FlexibleKeyHeader.java | 65 ++ .../keychain/ui/adapter/FlexibleKeyItem.java | 236 ++++++ .../ui/adapter/FlexibleKeyItemFactory.java | 60 ++ .../keychain/ui/adapter/KeyAdapter.java | 31 +- .../ui/adapter/KeySectionedListAdapter.java | 691 ------------------ .../ui/util/adapter/SectionCursorAdapter.java | 337 --------- .../res/drawable-v21/list_item_ripple.xml | 1 + .../src/main/res/layout/key_list_fragment.xml | 30 +- .../res/layout/key_list_header_public.xml | 8 +- .../src/main/res/layout/key_list_item.xml | 30 +- .../org/sufficientlysecure/keychain/Certs.sq | 4 +- .../keychain/KeyRingsPublic.sq | 2 +- .../org/sufficientlysecure/keychain/Keys.sq | 44 +- .../keychain/UserPackets.sq | 15 + 23 files changed, 746 insertions(+), 1778 deletions(-) delete mode 100644 OpenKeychain/src/main/java/android/support/v4/widget/NoScrollableSwipeRefreshLayout.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 0163bc22e..cf5fdd6ed 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -36,7 +36,9 @@ dependencies { // RecyclerView compile 'com.tonicartos:superslim:0.4.13' - compile 'com.futuremind.recyclerfastscroll:fastscroll:0.2.4' + compile 'eu.davidea:flexible-adapter:5.0.5' + compile 'eu.davidea:flexible-adapter-ui:1.0.0-b5' + compile 'eu.davidea:flexible-adapter-livedata:1.0.0-b2' // Material Drawer compile 'com.mikepenz:materialdrawer:5.6.0@aar' @@ -107,6 +109,7 @@ dependencies { // Comment out the libs referenced as git submodules! dependencyVerification { verify = [ + 'eu.davidea:flexible-adapter-ui:7ed5327d15c823e5fcf7d6e1017d8a47d079d1adc7141858f3cb427517ef35cd', 'com.android.support:design:7225973f7ee03765008a9c2f17a40b154c6885169fef022276e811c926a2202c', 'com.journeyapps:zxing-android-embedded:2422d83c2c09a7b645f516c8458ececba6a7da47b94e40778d876facf495c660', 'org.sufficientlysecure:donations:2be4183afa5e35263e37346344cfea48681f3c987e6832dd4acde227c13ccad6', @@ -115,6 +118,7 @@ dependencyVerification { 'com.mikepenz:materialize:942ccf5e2aa1a46803aa884e8dc7bbaf2a9e8e9996a0cf92e3fe2f44a8592ba4', 'com.android.support:appcompat-v7:0c7808fbbc5838d831e32e3c0a6f84e1f2c981deb8f11e010650f2b57923a335', 'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f', + 'eu.davidea:flexible-adapter:560e940e8cf0f4ed8f632f5f89527deeda7a61cce5f02f42cc0983f7c0d2de5f', 'com.android.support:recyclerview-v7:d735e4727878e99ef3980c10d15dc3468462fd509d4fb60cb8bd20b0f735085c', 'com.android.support:cardview-v7:8ed955dd037d82a7b4bbcaedb4f896523c3e4c1bf3ca698ce807c350767a2886', 'org.sufficientlysecure:html-textview:ed740adf05cae2373999c7a3047c803183d9807b2cf66162902090d7c112a832', @@ -145,6 +149,7 @@ dependencyVerification { 'org.apache.james:apache-mime4j-core:561987f604911e1870b2b4eabf0b0658d666c66cb1e65fba3e9e4bffe63acab9', 'com.splitwise:tokenautocomplete:f921f83ee26b5265f719b312c30452ef8e219557826c5ce5bf02e29647967939', 'com.cocosw:bottomsheet:85bd91fd837b02ebd7a888501cb26035c7cd985a6aa87303fca249da8231a2c3', + 'eu.davidea:flexible-adapter-livedata:c8718b46ff4fbf290ea18f0c5bfe8326badeadf5fd95899a1404c561a24f48a1', 'com.mikepenz:materialdrawer:8bba1428dcef5ad7c2decf49c612ad980b38e2f1031cbd66c152a8a104793929', 'com.mikepenz:iconics-core:478d7e245098f7c28b5b20a0e6b1e5cb108ef3eaf595af7190bc60f91063aa3d', 'com.mikepenz:google-material-typeface:f27c629ba5d2a90ecfbd7f221ff98cd363e1ee6be06b099b82bae490766e14a5', diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java index c408c2266..aef83afb0 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java @@ -28,12 +28,10 @@ import android.widget.ViewAnimator; import com.nispok.snackbar.Snackbar; -import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; -import org.sufficientlysecure.keychain.ui.adapter.KeySectionedListAdapter; import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; @@ -90,15 +88,14 @@ public abstract class CustomMatchers { } public static Matcher withKeyHolderId(final long keyId) { - return new BoundedMatcher - (KeySectionedListAdapter.KeyItemViewHolder.class) { + return new BoundedMatcher(RecyclerView.ViewHolder.class) { @Override public void describeTo(Description description) { description.appendText("with ViewHolder id: " + keyId); } @Override - protected boolean matchesSafely(KeySectionedListAdapter.KeyItemViewHolder item) { + protected boolean matchesSafely(View item) { return item.getItemId() == keyId; } }; diff --git a/OpenKeychain/src/main/java/android/support/v4/widget/NoScrollableSwipeRefreshLayout.java b/OpenKeychain/src/main/java/android/support/v4/widget/NoScrollableSwipeRefreshLayout.java deleted file mode 100644 index 19570350e..000000000 --- a/OpenKeychain/src/main/java/android/support/v4/widget/NoScrollableSwipeRefreshLayout.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v4.widget; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Transformation; -import android.widget.AbsListView; - - -/** - * Same as SwipeRefreshLayout, only updateContentOffsetTop and REFRESH_TRIGGER_DISTANCE - * have been modified! - */ -public class NoScrollableSwipeRefreshLayout extends ViewGroup { - private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300; - private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f; - private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; - private static final float PROGRESS_BAR_HEIGHT = 4; - private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f; - private static final int REFRESH_TRIGGER_DISTANCE = 200; - - private SwipeProgressBar mProgressBar; //the thing that shows progress is going - private View mTarget; //the content that gets pulled down - private int mOriginalOffsetTop; - private OnRefreshListener mListener; - private MotionEvent mDownEvent; - private int mFrom; - private boolean mRefreshing = false; - private int mTouchSlop; - private float mDistanceToTriggerSync = -1; - private float mPrevY; - private int mMediumAnimationDuration; - private float mFromPercentage = 0; - private float mCurrPercentage = 0; - private int mProgressBarHeight; - private int mCurrentTargetOffsetTop; - // Target is returning to its start offset because it was cancelled or a - // refresh was triggered. - private boolean mReturningToStart; - private final DecelerateInterpolator mDecelerateInterpolator; - private final AccelerateInterpolator mAccelerateInterpolator; - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.enabled - }; - - private final Animation mAnimateToStartPosition = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - int targetTop = 0; - if (mFrom != mOriginalOffsetTop) { - targetTop = (mFrom + (int)((mOriginalOffsetTop - mFrom) * interpolatedTime)); - } - int offset = targetTop - mTarget.getTop(); - final int currentTop = mTarget.getTop(); - if (offset + currentTop < 0) { - offset = 0 - currentTop; - } - setTargetOffsetTopAndBottom(offset); - } - }; - - private Animation mShrinkTrigger = new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime); - mProgressBar.setTriggerPercentage(percent); - } - }; - - private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - // Once the target content has returned to its start position, reset - // the target offset to 0 - mCurrentTargetOffsetTop = 0; - } - }; - - private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() { - @Override - public void onAnimationEnd(Animation animation) { - mCurrPercentage = 0; - } - }; - - private final Runnable mReturnToStartPosition = new Runnable() { - - @Override - public void run() { - mReturningToStart = true; - animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), - mReturnToStartPositionListener); - } - - }; - - // Cancel the refresh gesture and animate everything back to its original state. - private final Runnable mCancel = new Runnable() { - - @Override - public void run() { - mReturningToStart = true; - // Timeout fired since the user last moved their finger; animate the - // trigger to 0 and put the target back at its original position - if (mProgressBar != null) { - mFromPercentage = mCurrPercentage; - mShrinkTrigger.setDuration(mMediumAnimationDuration); - mShrinkTrigger.setAnimationListener(mShrinkAnimationListener); - mShrinkTrigger.reset(); - mShrinkTrigger.setInterpolator(mDecelerateInterpolator); - startAnimation(mShrinkTrigger); - } - animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), - mReturnToStartPositionListener); - } - - }; - - /** - * Simple constructor to use when creating a SwipeRefreshLayout from code. - * @param context - */ - public NoScrollableSwipeRefreshLayout(Context context) { - this(context, null); - } - - /** - * Constructor that is called when inflating SwipeRefreshLayout from XML. - * @param context - * @param attrs - */ - public NoScrollableSwipeRefreshLayout(Context context, AttributeSet attrs) { - super(context, attrs); - - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - - mMediumAnimationDuration = getResources().getInteger( - android.R.integer.config_mediumAnimTime); - - setWillNotDraw(false); - mProgressBar = new SwipeProgressBar(this); - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT); - mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); - mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR); - - final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - setEnabled(a.getBoolean(0, true)); - a.recycle(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - removeCallbacks(mCancel); - removeCallbacks(mReturnToStartPosition); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - removeCallbacks(mReturnToStartPosition); - removeCallbacks(mCancel); - } - - private void animateOffsetToStartPosition(int from, AnimationListener listener) { - mFrom = from; - mAnimateToStartPosition.reset(); - mAnimateToStartPosition.setDuration(mMediumAnimationDuration); - mAnimateToStartPosition.setAnimationListener(listener); - mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); - mTarget.startAnimation(mAnimateToStartPosition); - } - - /** - * Set the listener to be notified when a refresh is triggered via the swipe - * gesture. - */ - public void setOnRefreshListener(OnRefreshListener listener) { - mListener = listener; - } - - private void setTriggerPercentage(float percent) { - if (percent == 0f) { - // No-op. A null trigger means it's uninitialized, and setting it to zero-percent - // means we're trying to reset state, so there's nothing to reset in this case. - mCurrPercentage = 0; - return; - } - mCurrPercentage = percent; - mProgressBar.setTriggerPercentage(percent); - } - - /** - * Notify the widget that refresh state has changed. Do not call this when - * refresh is triggered by a swipe gesture. - * - * @param refreshing Whether or not the view should show refresh progress. - */ - public void setRefreshing(boolean refreshing) { - if (mRefreshing != refreshing) { - ensureTarget(); - mCurrPercentage = 0; - mRefreshing = refreshing; - if (mRefreshing) { - mProgressBar.start(); - } else { - mProgressBar.stop(); - } - } - } - - /** - * Set the four colors used in the progress animation. The first color will - * also be the color of the bar that grows in response to a user swipe - * gesture. - * - * @param colorRes1 Color resource. - * @param colorRes2 Color resource. - * @param colorRes3 Color resource. - * @param colorRes4 Color resource. - */ - public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) { - ensureTarget(); - final Resources res = getResources(); - final int color1 = res.getColor(colorRes1); - final int color2 = res.getColor(colorRes2); - final int color3 = res.getColor(colorRes3); - final int color4 = res.getColor(colorRes4); - mProgressBar.setColorScheme(color1, color2, color3,color4); - } - - /** - * @return Whether the SwipeRefreshWidget is actively showing refresh - * progress. - */ - public boolean isRefreshing() { - return mRefreshing; - } - - private void ensureTarget() { - // Don't bother getting the parent height if the parent hasn't been laid out yet. - if (mTarget == null) { - if (getChildCount() > 1 && !isInEditMode()) { - throw new IllegalStateException( - "SwipeRefreshLayout can host only one direct child"); - } - mTarget = getChildAt(0); - mOriginalOffsetTop = mTarget.getTop() + getPaddingTop(); - } - if (mDistanceToTriggerSync == -1) { - if (getParent() != null && ((View)getParent()).getHeight() > 0) { - final DisplayMetrics metrics = getResources().getDisplayMetrics(); - mDistanceToTriggerSync = (int) Math.min( - ((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR, - REFRESH_TRIGGER_DISTANCE * metrics.density); - } - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - mProgressBar.draw(canvas); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final int width = getMeasuredWidth(); - final int height = getMeasuredHeight(); - mProgressBar.setBounds(0, 0, width, mProgressBarHeight); - if (getChildCount() == 0) { - return; - } - final View child = getChildAt(0); - final int childLeft = getPaddingLeft(); - final int childTop = mCurrentTargetOffsetTop + getPaddingTop(); - final int childWidth = width - getPaddingLeft() - getPaddingRight(); - final int childHeight = height - getPaddingTop() - getPaddingBottom(); - child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); - } - - @Override - public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (getChildCount() > 1 && !isInEditMode()) { - throw new IllegalStateException("SwipeRefreshLayout can host only one direct child"); - } - if (getChildCount() > 0) { - getChildAt(0).measure( - MeasureSpec.makeMeasureSpec( - getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec( - getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), - MeasureSpec.EXACTLY)); - } - } - - /** - * @return Whether it is possible for the child view of this layout to - * scroll up. Override this if the child view is a custom view. - */ - public boolean canChildScrollUp() { - if (android.os.Build.VERSION.SDK_INT < 14) { - if (mTarget instanceof AbsListView) { - final AbsListView absListView = (AbsListView) mTarget; - return absListView.getChildCount() > 0 - && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) - .getTop() < absListView.getPaddingTop()); - } else { - return mTarget.getScrollY() > 0; - } - } else { - return ViewCompat.canScrollVertically(mTarget, -1); - } - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - ensureTarget(); - boolean handled = false; - if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) { - mReturningToStart = false; - } - if (isEnabled() && !mReturningToStart && !canChildScrollUp()) { - handled = onTouchEvent(ev); - } - return !handled ? super.onInterceptTouchEvent(ev) : handled; - } - - @Override - public void requestDisallowInterceptTouchEvent(boolean b) { - // Nope. - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); - boolean handled = false; - switch (action) { - case MotionEvent.ACTION_DOWN: - mCurrPercentage = 0; - mDownEvent = MotionEvent.obtain(event); - mPrevY = mDownEvent.getY(); - break; - case MotionEvent.ACTION_MOVE: - if (mDownEvent != null && !mReturningToStart) { - final float eventY = event.getY(); - float yDiff = eventY - mDownEvent.getY(); - if (yDiff > mTouchSlop) { - // User velocity passed min velocity; trigger a refresh - if (yDiff > mDistanceToTriggerSync) { - // User movement passed distance; trigger a refresh - startRefresh(); - handled = true; - break; - } else { - // Just track the user's movement - setTriggerPercentage( - mAccelerateInterpolator.getInterpolation( - yDiff / mDistanceToTriggerSync)); - float offsetTop = yDiff; - if (mPrevY > eventY) { - offsetTop = yDiff - mTouchSlop; - } - updateContentOffsetTop((int) (offsetTop)); - if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) { - // If the user puts the view back at the top, we - // don't need to. This shouldn't be considered - // cancelling the gesture as the user can restart from the top. - removeCallbacks(mCancel); - } else { - updatePositionTimeout(); - } - mPrevY = event.getY(); - handled = true; - } - } - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mDownEvent != null) { - mDownEvent.recycle(); - mDownEvent = null; - } - break; - } - return handled; - } - - private void startRefresh() { - removeCallbacks(mCancel); - mReturnToStartPosition.run(); - setRefreshing(true); - mListener.onRefresh(); - } - - private void updateContentOffsetTop(int targetTop) { - final int currentTop = mTarget.getTop(); - if (targetTop > mDistanceToTriggerSync) { - targetTop = (int) mDistanceToTriggerSync; - } else if (targetTop < 0) { - targetTop = 0; - } -// setTargetOffsetTopAndBottom(targetTop - currentTop); - } - - private void setTargetOffsetTopAndBottom(int offset) { - mTarget.offsetTopAndBottom(offset); - mCurrentTargetOffsetTop = mTarget.getTop(); - } - - private void updatePositionTimeout() { - removeCallbacks(mCancel); - postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT); - } - - /** - * Classes that wish to be notified when the swipe gesture correctly - * triggers a refresh should implement this interface. - */ - public interface OnRefreshListener { - void onRefresh(); - } - - /** - * Simple AnimationListener to avoid having to implement unneeded methods in - * AnimationListeners. - */ - private class BaseAnimationListener implements AnimationListener { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - } -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java new file mode 100644 index 000000000..68efe8010 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java @@ -0,0 +1,41 @@ +package org.sufficientlysecure.keychain.livedata; + + +import java.util.ArrayList; +import java.util.List; + +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.content.Context; +import android.database.Cursor; + +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.model.Key; +import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; + + +public class KeyRingDao { + private final SupportSQLiteDatabase db; + + public static KeyRingDao getInstance(Context context) { + KeychainDatabase keychainDatabase = new KeychainDatabase(context); + + return new KeyRingDao(keychainDatabase.getWritableDatabase()); + } + + private KeyRingDao(SupportSQLiteDatabase writableDatabase) { + this.db = writableDatabase; + } + + public List getUnifiedKeyInfo() { + SqlDelightQuery query = Key.FACTORY.selectAllUnifiedKeyInfo(); + List result = new ArrayList<>(); + try (Cursor cursor = db.query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo unifiedKeyInfo = Key.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(unifiedKeyInfo); + } + } + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java index b6dcc32f8..1a5c177de 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java @@ -28,8 +28,8 @@ public abstract class AutocryptPeer implements AutocryptPeersModel { } public boolean isGossipKeyRevoked() { - Long revokedInt = gossip_key_is_revoked_int(); - return revokedInt != null && revokedInt != 0; + Boolean gossip_key_is_revoked = gossip_key_is_revoked_int(); + return gossip_key_is_revoked != null && gossip_key_is_revoked; } public boolean isGossipKeyExpired() { @@ -45,8 +45,8 @@ public abstract class AutocryptPeer implements AutocryptPeersModel { } public boolean isKeyRevoked() { - Long revokedInt = key_is_revoked_int(); - return revokedInt != null && revokedInt != 0; + Boolean revoked = key_is_revoked_int(); + return revoked != null && revoked; } public boolean isKeyExpired() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java new file mode 100644 index 000000000..523883bc1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java @@ -0,0 +1,50 @@ +package org.sufficientlysecure.keychain.model; + + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.KeysModel; + + +@AutoValue +public abstract class Key implements KeysModel { + public static final Factory FACTORY = new Factory<>(AutoValue_Key::new); + public static final SelectAllUnifiedKeyInfoMapper UNIFIED_KEY_INFO_MAPPER = + FACTORY.selectAllUnifiedKeyInfoMapper(AutoValue_Key_UnifiedKeyInfo::new); + + @AutoValue + public static abstract class UnifiedKeyInfo implements SelectAllUnifiedKeyInfoModel { + private List autocryptPackageNames; + + public boolean is_expired() { + Long expiry = expiry(); + return expiry != null && expiry * 1000 < System.currentTimeMillis(); + } + + public boolean has_any_secret() { + return has_any_secret_int() != 0; + } + + public boolean is_verified() { + Integer verified = verified(); + return verified != null && verified == 1; + } + + public boolean has_duplicate() { + return has_duplicate_int() != 0; + } + + public List autocrypt_package_names() { + if (autocryptPackageNames == null) { + String csv = autocrypt_package_names_csv(); + autocryptPackageNames = csv == null ? Collections.emptyList() : + Arrays.asList(csv.split(",")); + } + return autocryptPackageNames; + } + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index cd925c115..30192e3cc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -34,11 +34,15 @@ import android.database.SQLException; import android.database.sqlite.SQLiteException; import android.provider.BaseColumns; +import org.sufficientlysecure.keychain.ApiAppsModel; import org.sufficientlysecure.keychain.AutocryptPeersModel; +import org.sufficientlysecure.keychain.CertsModel; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeyMetadataModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel; +import org.sufficientlysecure.keychain.UserPacketsModel; import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.model.Certification; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; @@ -120,26 +124,6 @@ public class KeychainDatabase { + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; - private static final String CREATE_CERTS = - "CREATE TABLE IF NOT EXISTS " + Tables.CERTS + "(" - + CertsColumns.MASTER_KEY_ID + " INTEGER," - + CertsColumns.RANK + " INTEGER, " // rank of certified uid - - + CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key - + CertsColumns.TYPE + " INTEGER, " - + CertsColumns.VERIFIED + " INTEGER, " - + CertsColumns.CREATION + " INTEGER, " - - + CertsColumns.DATA + " BLOB, " - - + "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", " - + CertsColumns.KEY_ID_CERTIFIER + "), " - + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE," - + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES " - + Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE" - + ")"; - private static final String CREATE_KEY_SIGNATURES = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " (" + KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " @@ -212,14 +196,14 @@ public class KeychainDatabase { db.execSQL(KeyRingsPublicModel.CREATE_TABLE); db.execSQL(CREATE_KEYS); - db.execSQL(CREATE_USER_PACKETS); - db.execSQL(CREATE_CERTS); + db.execSQL(UserPacketsModel.CREATE_TABLE); + db.execSQL(CertsModel.CREATE_TABLE); db.execSQL(KeyMetadataModel.CREATE_TABLE); db.execSQL(CREATE_KEY_SIGNATURES); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(AutocryptPeersModel.CREATE_TABLE); - db.execSQL(ApiApp.CREATE_TABLE); + db.execSQL(ApiAppsModel.CREATE_TABLE); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " 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 68e4e4844..77f51c37f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -23,18 +23,19 @@ import java.util.List; import android.animation.ObjectAnimator; import android.app.Activity; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Context; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SearchView; -import android.text.TextUtils; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; @@ -46,55 +47,59 @@ 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; -import com.tonicartos.superslim.LayoutManager; +import eu.davidea.fastscroller.FastScroller; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener; +import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener; +import eu.davidea.flexibleadapter.SelectableAdapter.Mode; +import eu.davidea.flexibleadapter.common.FlexibleItemDecoration; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; +import org.sufficientlysecure.keychain.livedata.KeyRingDao; +import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; -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.ui.adapter.KeySectionedListAdapter; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory; 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.keyview.loader.AsyncTaskLiveData; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; -public class KeyListFragment extends RecyclerFragment - implements SearchView.OnQueryTextListener, - LoaderManager.LoaderCallbacks, FabContainer { +public class KeyListFragment extends RecyclerFragment> + implements SearchView.OnQueryTextListener, OnItemClickListener, OnItemLongClickListener, FabContainer { static final int REQUEST_ACTION = 1; private static final int REQUEST_DELETE = 2; private static final int REQUEST_VIEW_KEY = 3; - // saves the mode object for multiselect, needed for reset at some point private ActionMode mActionMode = null; private Button vSearchButton; private ViewAnimator vSearchContainer; - private String mQuery; private FloatingActionsMenu mFab; - // Callbacks related to listview and menu events - private final ActionMode.Callback mActionCallback - = new ActionMode.Callback() { + private final ActionMode.Callback mActionCallback = new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - getActivity().getMenuInflater().inflate(R.menu.key_list_multi, menu); + mode.getMenuInflater().inflate(R.menu.key_list_multi, menu); return true; } @@ -107,28 +112,17 @@ public class KeyListFragment extends RecyclerFragment public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.menu_key_list_multi_encrypt: { - long[] keyIds = getAdapter().getSelectedMasterKeyIds(); - Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); - intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); - intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, keyIds); - - startActivityForResult(intent, REQUEST_ACTION); + long[] keyIds = getSelectedMasterKeyIds(); + multiSelectEncrypt(keyIds); mode.finish(); break; } case R.id.menu_key_list_multi_delete: { - long[] keyIds = getAdapter().getSelectedMasterKeyIds(); - boolean hasSecret = getAdapter().isAnySecretKeySelected(); - Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); - intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, keyIds); - intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); - if (hasSecret) { - intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, - Preferences.getPreferences(getActivity()).getPreferredKeyserver()); - } - - startActivityForResult(intent, REQUEST_DELETE); + long[] keyIds = getSelectedMasterKeyIds(); + boolean hasSecret = isAnySecretKeySelected(); + multiSelectDelete(keyIds, hasSecret); + mode.finish(); break; } } @@ -139,54 +133,61 @@ public class KeyListFragment extends RecyclerFragment public void onDestroyActionMode(ActionMode mode) { mActionMode = null; if (getAdapter() != null) { - getAdapter().finishSelection(); + getAdapter().clearSelection(); } } }; + private FastScroller fastScroller; - private final KeySectionedListAdapter.KeyListListener mKeyListener - = new KeySectionedListAdapter.KeyListListener() { - @Override - public void onKeyDummyItemClicked() { - createKey(); + private void multiSelectDelete(long[] keyIds, boolean hasSecret) { + Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, keyIds); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); + if (hasSecret) { + intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(getActivity()).getPreferredKeyserver()); } + startActivityForResult(intent, REQUEST_DELETE); + } - @Override - public void onKeyItemClicked(long masterKeyId) { - Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); - viewIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - startActivityForResult(viewIntent, REQUEST_VIEW_KEY); - } + private void multiSelectEncrypt(long[] keyIds) { + Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); + intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); + intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, keyIds); - @Override - public void onSlingerButtonClicked(long masterKeyId) { - Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); - startActivityForResult(safeSlingerIntent, REQUEST_ACTION); - } + startActivityForResult(intent, REQUEST_ACTION); + } - @Override - public void onSelectionStateChanged(int selectedCount) { - if (selectedCount < 1) { - if (mActionMode != null) { - mActionMode.finish(); - } - } else { - if (mActionMode == null) { - mActionMode = getActivity().startActionMode(mActionCallback); - } - - String keysSelected = getResources().getQuantityString( - R.plurals.key_list_selected_keys, selectedCount, selectedCount); - mActionMode.setTitle(keysSelected); + private long[] getSelectedMasterKeyIds() { + FlexibleAdapter adapter = getAdapter(); + List selectedPositions = adapter.getSelectedPositions(); + long[] keyIds = new long[selectedPositions.size()]; + for (int i = 0; i < selectedPositions.size(); i++) { + FlexibleKeyItem selectedItem = adapter.getItem(selectedPositions.get(i)); + if (selectedItem != null) { + keyIds[i] = selectedItem.keyInfo.master_key_id(); } } - }; + return keyIds; + } + private boolean isAnySecretKeySelected() { + FlexibleAdapter adapter = getAdapter(); + for (int position : adapter.getSelectedPositions()) { + FlexibleKeyItem item = adapter.getItem(position); + if (item != null && item.keyInfo.has_any_secret()) { + return true; + } + } + return false; + } + + public void startSafeSlingerForKey(long masterKeyId) { + Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + startActivityForResult(safeSlingerIntent, REQUEST_ACTION); + } - /** - * Load custom layout - */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.key_list_fragment, container, false); @@ -210,6 +211,11 @@ public class KeyListFragment extends RecyclerFragment importFile(); }); + fastScroller = view.findViewById(R.id.fast_scroller); + + vSearchContainer = view.findViewById(R.id.search_container); + vSearchButton = view.findViewById(R.id.search_button); + vSearchButton.setOnClickListener(v -> startSearchForQuery()); return view; } @@ -222,7 +228,10 @@ public class KeyListFragment extends RecyclerFragment super.onActivityCreated(savedInstanceState); // show app name instead of "keys" from nav drawer - final FragmentActivity activity = getActivity(); + FragmentActivity activity = getActivity(); + if (activity == null) { + throw new NullPointerException("Activity must be bound!"); + } activity.setTitle(R.string.app_name); // We have a menu item to show in action bar. @@ -231,23 +240,64 @@ public class KeyListFragment extends RecyclerFragment // Start out with a progress indicator. hideList(false); - // click on search button (in empty view) starts query for search string - vSearchContainer = activity.findViewById(R.id.search_container); - vSearchButton = activity.findViewById(R.id.search_button); - vSearchButton.setOnClickListener(v -> startSearchForQuery()); + setLayoutManager(new LinearLayoutManager(activity)); - KeySectionedListAdapter adapter = new KeySectionedListAdapter(getContext(), null); - adapter.setKeyListener(mKeyListener); + KeyListViewModel keyListViewModel = ViewModelProviders.of(this).get(KeyListViewModel.class); + keyListViewModel.getLiveData(getContext()).observe(this, this::onLoadKeyItems); + } - setAdapter(adapter); - setLayoutManager(new LayoutManager(getActivity())); + public static class KeyListViewModel extends ViewModel { + LiveData> liveData; - FastScroller fastScroller = getActivity().findViewById(R.id.fastscroll); - fastScroller.setRecyclerView(getRecyclerView()); + LiveData> getLiveData(Context context) { + if (liveData == null) { + liveData = new KeyListLiveData(context); + } + return liveData; + } + } - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); + public static class KeyListLiveData extends AsyncTaskLiveData> { + private final KeyRingDao keyRingDao; + private FlexibleKeyItemFactory flexibleKeyItemFactory; + + KeyListLiveData(@NonNull Context context) { + super(context, KeyRings.CONTENT_URI); + keyRingDao = KeyRingDao.getInstance(context.getApplicationContext()); + flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources()); + } + + @Override + protected List asyncLoadData() { + List unifiedKeyInfo = keyRingDao.getUnifiedKeyInfo(); + return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo); + } + } + + private void onLoadKeyItems(List flexibleKeyItems) { + FlexibleAdapter adapter = getAdapter(); + if (adapter == null) { + adapter = new FlexibleAdapter<>(flexibleKeyItems, this, true); + adapter.setDisplayHeadersAtStartUp(true); + adapter.setStickyHeaders(true); + adapter.setMode(Mode.MULTI); + setAdapter(adapter); + adapter.setFastScroller(fastScroller); + fastScroller.setBubbleTextCreator(this::getBubbleText); + } else { + adapter.updateDataSet(flexibleKeyItems, true); + } + + showList(true); + } + + private String getBubbleText(int position) { + FlexibleKeyItem item = getAdapter().getItem(position); + if (item == null) { + return ""; + } + FlexibleKeyHeader header = item.getHeader(); + return header.getSectionTitle(); } @Override @@ -305,53 +355,11 @@ public class KeyListFragment extends RecyclerFragment } Intent searchIntent = new Intent(activity, ImportKeysActivity.class); - searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, mQuery); + searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, getAdapter().getFilter(String.class)); searchIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); startActivity(searchIntent); } - @Override - public Loader onCreateLoader(int id, Bundle args) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - Uri uri; - if (!TextUtils.isEmpty(mQuery)) { - uri = KeyRings.buildUnifiedKeyRingsFindByUserIdUri(mQuery); - } else { - uri = KeyRings.buildUnifiedKeyRingsUri(); - } - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), uri, - KeySectionedListAdapter.KeyListCursor.PROJECTION, null, null, - KeySectionedListAdapter.KeyListCursor.ORDER); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - getAdapter().setSearchQuery(mQuery); - getAdapter().swapCursor(KeySectionedListAdapter.KeyListCursor.wrap(data)); - - // end action mode, if any - if (mActionMode != null) { - mActionMode.finish(); - } - - // The list should now be shown. - showList(isResumed()); - } - - @Override - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - getAdapter().swapCursor(null); - } - @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.key_list, menu); @@ -375,19 +383,13 @@ public class KeyListFragment extends RecyclerFragment MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { - - // disable swipe-to-refresh - // mSwipeRefreshLayout.setIsLocked(true); return true; } @Override public boolean onMenuItemActionCollapse(MenuItem item) { - mQuery = null; - getLoaderManager().restartLoader(0, null, KeyListFragment.this); - - // enable swipe-to-refresh - // mSwipeRefreshLayout.setIsLocked(false); + getAdapter().setFilter(null); + getAdapter().filterItems(); return true; } }); @@ -395,6 +397,54 @@ public class KeyListFragment extends RecyclerFragment super.onCreateOptionsMenu(menu, inflater); } + @Override + public boolean onItemClick(View view, int position) { + FlexibleKeyItem item = getAdapter().getItem(position); + if (item == null) { + return false; + } + + if (mActionMode != null && position != RecyclerView.NO_POSITION) { + toggleSelection(position); + return true; + } + + long masterKeyId = item.keyInfo.master_key_id(); + Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); + viewIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + startActivityForResult(viewIntent, REQUEST_VIEW_KEY); + return false; + } + + @Override + public void onItemLongClick(int position) { + if (mActionMode == null) { + FragmentActivity activity = getActivity(); + if (activity != null) { + mActionMode = activity.startActionMode(mActionCallback); + } + } + toggleSelection(position); + } + + private void toggleSelection(int position) { + getAdapter().toggleSelection(position); + + int count = getAdapter().getSelectedItemCount(); + + if (count == 0) { + mActionMode.finish(); + } else { + setContextTitle(count); + } + } + + private void setContextTitle(int selectedCount) { + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, selectedCount, selectedCount); + mActionMode.setTitle(keysSelected); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -410,7 +460,7 @@ public class KeyListFragment extends RecyclerFragment try { KeychainDatabase.debugBackup(getActivity(), true); Notify.create(getActivity(), "Restored debug_backup.db", Notify.Style.OK).show(); - getActivity().getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null); + getActivity().getContentResolver().notifyChange(KeyRings.CONTENT_URI, null); } catch (IOException e) { Timber.e(e, "IO Error"); Notify.create(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR).show(); @@ -456,20 +506,12 @@ public class KeyListFragment extends RecyclerFragment } @Override - public boolean onQueryTextChange(String s) { - Timber.d("onQueryTextChange s: %s", s); - // Called when the action bar search text has changed. Update the - // search filter, and restart the loader to do a new query with this - // filter. - // If the nav drawer is opened, onQueryTextChange("") is executed. - // This hack prevents restarting the loader. - if (!s.equals(mQuery)) { - mQuery = s; - getLoaderManager().restartLoader(0, null, this); - } + public boolean onQueryTextChange(String searchText) { + getAdapter().setFilter(searchText); + getAdapter().filterItems(300); - if (s.length() > 2) { - vSearchButton.setText(getString(R.string.btn_search_for_query, mQuery)); + if (searchText.length() > 2) { + vSearchButton.setText(getString(R.string.btn_search_for_query, searchText)); vSearchContainer.setDisplayedChild(1); vSearchContainer.setVisibility(View.VISIBLE); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 9173861e4..4092453c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui; + +import java.io.IOException; +import java.io.OutputStream; + import android.app.Activity; import android.content.Intent; import android.net.Uri; @@ -26,19 +30,15 @@ import android.view.MenuInflater; import android.view.MenuItem; import com.tonicartos.superslim.LayoutManager; - import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.adapter.NestedLogAdapter; +import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.dialog.ShareLogDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; - -import java.io.IOException; -import java.io.OutputStream; public class LogDisplayFragment extends RecyclerFragment diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java new file mode 100644 index 000000000..fda48678f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java @@ -0,0 +1,65 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.List; + +import android.view.View; +import android.widget.TextView; + +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractHeaderItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader.FlexibleHeaderViewHolder; + + +public class FlexibleKeyHeader extends AbstractHeaderItem { + private final String sectionTitle; + + FlexibleKeyHeader(String sectionTitle) { + this.sectionTitle = sectionTitle; + setEnabled(false); + setSelectable(false); + } + + @Override + public boolean equals(Object o) { + if (o instanceof FlexibleKeyHeader) { + FlexibleKeyHeader other = (FlexibleKeyHeader) o; + return sectionTitle.equals(other.sectionTitle); + } + return false; + } + + @Override + public int getLayoutRes() { + return R.layout.key_list_header_public; + } + + public String getSectionTitle() { + return sectionTitle; + } + + @Override + public FlexibleHeaderViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new FlexibleHeaderViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, FlexibleHeaderViewHolder holder, int position, + List payloads) { + holder.text1.setText(sectionTitle); + } + + static class FlexibleHeaderViewHolder extends FlexibleViewHolder { + final TextView text1; + + FlexibleHeaderViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter, true); + text1 = itemView.findViewById(android.R.id.text1); + } + + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java new file mode 100644 index 000000000..cd9f8726e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java @@ -0,0 +1,236 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.List; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractSectionableItem; +import eu.davidea.flexibleadapter.items.IFilterable; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleKeyItemViewHolder; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; + + +public class FlexibleKeyItem extends AbstractSectionableItem + implements IFilterable { + public final UnifiedKeyInfo keyInfo; + + FlexibleKeyItem(UnifiedKeyInfo keyInfo, FlexibleKeyHeader header) { + super(header); + this.keyInfo = keyInfo; + + setSelectable(true); + } + + @Override + public int getLayoutRes() { + return R.layout.key_list_item; + } + + @Override + public FlexibleKeyItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new FlexibleKeyItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder( + FlexibleAdapter adapter, FlexibleKeyItemViewHolder holder, int position, List payloads) { + String highlightString = adapter.getFilter(String.class); + holder.bind(keyInfo, highlightString); + } + + @Override + public boolean equals(Object o) { + if (o instanceof FlexibleKeyItem) { + FlexibleKeyItem other = (FlexibleKeyItem) o; + return keyInfo.master_key_id() == other.keyInfo.master_key_id(); + } + return false; + } + + @Override + public int hashCode() { + long masterKeyId = keyInfo.master_key_id(); + return (int) (masterKeyId ^ (masterKeyId >>> 32)); + } + + @Override + public boolean filter(String constraint) { + String uidList = keyInfo.user_id_list(); + return constraint == null || (uidList != null && uidList.contains(constraint)); + } + + public class FlexibleKeyItemViewHolder extends FlexibleViewHolder { + private static final long JUST_NOW_THRESHOLD = DateUtils.MINUTE_IN_MILLIS * 5; + + private final TextView vMainUserId; + private final TextView vMainUserIdRest; + private final TextView vCreationDate; + private final ImageView vStatusIcon; + private final ImageView vTrustIdIcon; + + FlexibleKeyItemViewHolder(View itemView, FlexibleAdapter adapter) { + super(itemView, adapter); + + vMainUserId = itemView.findViewById(R.id.key_list_item_name); + vMainUserIdRest = itemView.findViewById(R.id.key_list_item_email); + vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon); + vCreationDate = itemView.findViewById(R.id.key_list_item_creation); + vTrustIdIcon = itemView.findViewById(R.id.key_list_item_tid_icon); + } + + public void bind(UnifiedKeyInfo keyInfo, String highlightString) { + setEnabled(true); + + Context context = itemView.getContext(); + Highlighter highlighter = new Highlighter(context, highlightString); + + { // set name and stuff, common to both key types + if (keyInfo.name() == null) { + if (keyInfo.email() != null) { + vMainUserId.setText(highlighter.highlight(keyInfo.email())); + vMainUserIdRest.setVisibility(View.GONE); + } else { + vMainUserId.setText(R.string.user_id_no_name); + } + } else { + vMainUserId.setText(highlighter.highlight(keyInfo.name())); + // for some reason, this hangs for me + // FlexibleUtils.highlightText(vMainUserId, keyInfo.name(), highlightString); + if (keyInfo.email() != null) { + vMainUserIdRest.setText(highlighter.highlight(keyInfo.email())); + vMainUserIdRest.setVisibility(View.VISIBLE); + } else { + vMainUserIdRest.setVisibility(View.GONE); + } + } + } + + { // set edit button and status, specific by key type. Note: order is important! + int textColor; + if (keyInfo.is_revoked()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + vStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (keyInfo.is_expired()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + vStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (!keyInfo.is_secure()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + null, + KeyFormattingUtils.State.INSECURE, + R.color.key_flag_gray + ); + + vStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (keyInfo.has_any_secret()) { + vStatusIcon.setVisibility(View.GONE); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } else { + // this is a public key - show if it's verified + if (keyInfo.is_verified()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + KeyFormattingUtils.State.VERIFIED + ); + + vStatusIcon.setVisibility(View.VISIBLE); + } else { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + KeyFormattingUtils.State.UNVERIFIED + ); + + vStatusIcon.setVisibility(View.VISIBLE); + } + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + vMainUserId.setTextColor(textColor); + vMainUserIdRest.setTextColor(textColor); + + if (keyInfo.has_duplicate() || keyInfo.has_any_secret()) { + vCreationDate.setText(getSecretKeyReadableTime(context, keyInfo)); + vCreationDate.setTextColor(textColor); + vCreationDate.setVisibility(View.VISIBLE); + } else { + vCreationDate.setVisibility(View.GONE); + } + } + + { // set icons + + if (!keyInfo.has_any_secret() && !keyInfo.autocrypt_package_names().isEmpty()) { + String packageName = keyInfo.autocrypt_package_names().get(0); + Drawable drawable = PackageIconGetter.getInstance(context).getDrawableForPackageName(packageName); + if (drawable != null) { + vTrustIdIcon.setImageDrawable(drawable); + vTrustIdIcon.setVisibility(View.VISIBLE); + } else { + vTrustIdIcon.setVisibility(View.GONE); + } + } else { + vTrustIdIcon.setVisibility(View.GONE); + } + } + } + + + @NonNull + private String getSecretKeyReadableTime(Context context, UnifiedKeyInfo keyInfo) { + long creationMillis = keyInfo.creation() * 1000; + + boolean allowRelativeTimestamp = keyInfo.has_duplicate(); + if (allowRelativeTimestamp) { + long creationAgeMillis = System.currentTimeMillis() - creationMillis; + if (creationAgeMillis < JUST_NOW_THRESHOLD) { + return context.getString(R.string.label_key_created_just_now); + } + } + + String dateTime = DateUtils.formatDateTime(context, + creationMillis, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + return context.getString(R.string.label_key_created, dateTime); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java new file mode 100644 index 000000000..7b89a64c1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java @@ -0,0 +1,60 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.content.res.Resources; +import android.support.annotation.NonNull; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; + + +public class FlexibleKeyItemFactory { + private Map initialsHeaderMap = new HashMap<>(); + private FlexibleKeyHeader myKeysHeader; + + public FlexibleKeyItemFactory(Resources resources) { + String myKeysHeaderText = resources.getString(R.string.my_keys); + myKeysHeader = new FlexibleKeyHeader(myKeysHeaderText); + } + + public List mapUnifiedKeyInfoToFlexibleKeyItems(List unifiedKeyInfos) { + List result = new ArrayList<>(); + if (unifiedKeyInfos == null) { + return result; + } + for (UnifiedKeyInfo unifiedKeyInfo : unifiedKeyInfos) { + FlexibleKeyHeader header = getFlexibleKeyHeader(unifiedKeyInfo); + FlexibleKeyItem flexibleKeyItem = new FlexibleKeyItem(unifiedKeyInfo, header); + result.add(flexibleKeyItem); + } + return result; + } + + private FlexibleKeyHeader getFlexibleKeyHeader(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo.has_any_secret()) { + return myKeysHeader; + } + + String headerText = getHeaderText(unifiedKeyInfo); + + FlexibleKeyHeader header; + if (initialsHeaderMap.containsKey(headerText)) { + header = initialsHeaderMap.get(headerText); + } else { + header = new FlexibleKeyHeader(headerText); + initialsHeaderMap.put(headerText, header); + } + return header; + } + + @NonNull + private String getHeaderText(UnifiedKeyInfo unifiedKeyInfo) { + String headerText = unifiedKeyInfo.name(); + return headerText == null || headerText.isEmpty() ? "" : headerText.substring(0, 1).toUpperCase(); + } +} 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 f077704b6..9912cb697 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 @@ -17,15 +17,20 @@ package org.sufficientlysecure.keychain.ui.adapter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + import android.content.Context; import android.database.Cursor; -import android.graphics.PorterDuff; import android.support.v4.widget.CursorAdapter; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -40,12 +45,6 @@ import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; - public class KeyAdapter extends CursorAdapter { protected String mQuery; @@ -105,8 +104,6 @@ public class KeyAdapter extends CursorAdapter { public TextView mMainUserIdRest; public TextView mCreationDate; public ImageView mStatus; - public View mSlinger; - public ImageButton mSlingerButton; public KeyItem mDisplayedItem; @@ -116,8 +113,6 @@ public class KeyAdapter extends CursorAdapter { mMainUserId = view.findViewById(R.id.key_list_item_name); mMainUserIdRest = view.findViewById(R.id.key_list_item_email); mStatus = view.findViewById(R.id.key_list_item_status_icon); - mSlinger = view.findViewById(R.id.key_list_item_slinger_view); - mSlingerButton = view.findViewById(R.id.key_list_item_slinger_button); mCreationDate = view.findViewById(R.id.key_list_item_creation); } @@ -154,28 +149,17 @@ public class KeyAdapter extends CursorAdapter { KeyFormattingUtils .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsExpired) { KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (!item.mIsSecure) { KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsSecret) { mStatus.setVisibility(View.GONE); - if (mSlingerButton.hasOnClickListeners()) { - mSlingerButton.setColorFilter( - FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText), - PorterDuff.Mode.SRC_IN); - mSlinger.setVisibility(View.VISIBLE); - } else { - mSlinger.setVisibility(View.GONE); - } textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); } else { // this is a public key - show if it's verified @@ -186,7 +170,6 @@ public class KeyAdapter extends CursorAdapter { KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED); mStatus.setVisibility(View.VISIBLE); } - mSlinger.setVisibility(View.GONE); textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java deleted file mode 100644 index c82bc9500..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySectionedListAdapter.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import com.futuremind.recyclerviewfastscroll.SectionTitleProvider; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; -import org.sufficientlysecure.keychain.ui.util.adapter.SectionCursorAdapter; -import timber.log.Timber; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -public class KeySectionedListAdapter extends SectionCursorAdapter implements SectionTitleProvider { - - private static final short VIEW_ITEM_TYPE_KEY = 0x0; - private static final short VIEW_ITEM_TYPE_DUMMY = 0x1; - - private static final short VIEW_SECTION_TYPE_PRIVATE = 0x0; - private static final short VIEW_SECTION_TYPE_PUBLIC = 0x1; - - private static final long JUST_NOW_TIMESPAN = DateUtils.MINUTE_IN_MILLIS * 5; - - private String mQuery; - private List mSelected; - private KeyListListener mListener; - - private boolean mHasDummy = false; - - public KeySectionedListAdapter(Context context, Cursor cursor) { - super(context, KeyListCursor.wrap(cursor, KeyListCursor.class), 0); - - mQuery = ""; - mSelected = new ArrayList<>(); - } - - public void setSearchQuery(String query) { - mQuery = query; - } - - - @Override - public void onContentChanged() { - mHasDummy = false; - mSelected.clear(); - - if (mListener != null) { - mListener.onSelectionStateChanged(0); - } - - super.onContentChanged(); - } - - @Override - public KeyListCursor swapCursor(KeyListCursor cursor) { - if (cursor != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { - boolean isSecret = cursor.moveToFirst() && cursor.isSecret(); - - if (!isSecret) { - MatrixCursor headerCursor = new MatrixCursor(KeyListCursor.PROJECTION); - Long[] row = new Long[KeyListCursor.PROJECTION.length]; - row[cursor.getColumnIndex(KeychainContract.KeyRings.HAS_ANY_SECRET)] = 1L; - row[cursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID)] = 0L; - headerCursor.addRow(row); - - Cursor[] toMerge = { - headerCursor, - cursor.getWrappedCursor() - }; - - cursor = KeyListCursor.wrap(new MergeCursor(toMerge)); - } - } - - return super.swapCursor(cursor); - } - - public void setKeyListener(KeyListListener listener) { - mListener = listener; - } - - private int getSelectedCount() { - return mSelected.size(); - } - - private void selectPosition(int position) { - mSelected.add(position); - notifyItemChanged(position); - } - - private void deselectPosition(int position) { - mSelected.remove(Integer.valueOf(position)); - notifyItemChanged(position); - } - - private boolean isSelected(int position) { - return mSelected.contains(position); - } - - public long[] getSelectedMasterKeyIds() { - long[] keys = new long[mSelected.size()]; - for (int i = 0; i < keys.length; i++) { - int index = getCursorPositionWithoutSections(mSelected.get(i)); - if (!moveCursor(index)) { - return keys; - } - - keys[i] = getIdFromCursor(getCursor()); - } - - return keys; - } - - public boolean isAnySecretKeySelected() { - for (int i = 0; i < mSelected.size(); i++) { - int index = getCursorPositionWithoutSections(mSelected.get(i)); - if (!moveCursor(index)) { - return false; - } - - if (getCursor().isSecret()) { - return true; - } - } - - return false; - } - - - /** - * Returns the number of database entries displayed. - * - * @return The item count - */ - public int getCount() { - if (getCursor() != null) { - return getCursor().getCount() - (mHasDummy ? 1 : 0); - } else { - return 0; - } - } - - @Override - public long getIdFromCursor(KeyListCursor cursor) { - return cursor.getKeyId(); - } - - @Override - protected Character getSectionFromCursor(KeyListCursor cursor) throws IllegalStateException { - if (cursor.isSecret()) { - if (cursor.getKeyId() == 0L) { - mHasDummy = true; - } - - return '#'; - } else { - String name = cursor.getName(); - if (name != null) { - return Character.toUpperCase(name.charAt(0)); - } else { - return '?'; - } - } - } - - @Override - protected short getSectionHeaderViewType(int sectionIndex) { - return (sectionIndex < 1) ? - VIEW_SECTION_TYPE_PRIVATE : - VIEW_SECTION_TYPE_PUBLIC; - } - - @Override - protected short getSectionItemViewType(int position) { - if (moveCursor(position)) { - KeyListCursor c = getCursor(); - - if (c.isSecret() && c.getKeyId() == 0L) { - return VIEW_ITEM_TYPE_DUMMY; - } - } else { - Timber.w("Unable to determine key view type. " - + "Reason: Could not move cursor over dataset."); - } - - return VIEW_ITEM_TYPE_KEY; - } - - @Override - protected KeyHeaderViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_SECTION_TYPE_PUBLIC: - return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_header_public, parent, false)); - - case VIEW_SECTION_TYPE_PRIVATE: - return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_header_private, parent, false)); - - default: - return null; - } - } - - @Override - protected ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_ITEM_TYPE_KEY: - return new KeyItemViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_item, parent, false)); - - case VIEW_ITEM_TYPE_DUMMY: - return new KeyDummyViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.key_list_dummy, parent, false)); - - default: - return null; - } - - } - - @Override - protected void onBindSectionViewHolder(KeyHeaderViewHolder holder, Character section) { - switch (holder.getItemViewTypeWithoutSections()) { - case VIEW_SECTION_TYPE_PUBLIC: { - String title = section.equals('?') ? - getContext().getString(R.string.user_id_no_name) : - String.valueOf(section); - - holder.bind(title); - break; - } - - case VIEW_SECTION_TYPE_PRIVATE: { - int count = getCount(); - String title = getContext().getResources() - .getQuantityString(R.plurals.n_keys, count, count); - holder.bind(title); - break; - } - - } - } - - @Override - protected void onBindItemViewHolder(ViewHolder holder, KeyListCursor cursor) { - if (holder.getItemViewTypeWithoutSections() == VIEW_ITEM_TYPE_KEY) { - Highlighter highlighter = new Highlighter(getContext(), mQuery); - ((KeyItemViewHolder) holder).bindKey(cursor, highlighter); - } - } - - public void finishSelection() { - Integer[] selected = mSelected.toArray( - new Integer[mSelected.size()] - ); - - mSelected.clear(); - - for (Integer aSelected : selected) { - notifyItemChanged(aSelected); - } - } - - @Override - public String getSectionTitle(int position) { - // this String will be shown in a bubble for specified position - if (moveCursor(getCursorPositionWithoutSections(position))) { - KeyListCursor cursor = getCursor(); - - if (cursor.isSecret()) { - if (cursor.getKeyId() == 0L) { - mHasDummy = true; - } - - return "My"; - } else { - String name = cursor.getName(); - if (name != null) { - return name.substring(0, 1).toUpperCase(); - } else { - return null; - } - } - } else { - Timber.w("Unable to determine section title. " - + "Reason: Could not move cursor over dataset."); - return null; - } - } - - private class KeyDummyViewHolder extends SectionCursorAdapter.ViewHolder - implements View.OnClickListener { - - KeyDummyViewHolder(View itemView) { - super(itemView); - - itemView.setClickable(true); - itemView.setOnClickListener(this); - itemView.setEnabled(getSelectedCount() == 0); - } - - @Override - public void onClick(View view) { - if (mListener != null) { - mListener.onKeyDummyItemClicked(); - } - } - } - - public class KeyItemViewHolder extends SectionCursorAdapter.ViewHolder - implements View.OnClickListener, View.OnLongClickListener { - - private final ImageView mTrustIdIcon; - private final TextView mMainUserId; - private final TextView mMainUserIdRest; - private final TextView mCreationDate; - private final ImageView mStatus; - private final View mSlinger; - private final ImageButton mSlingerButton; - - KeyItemViewHolder(View itemView) { - super(itemView); - - mMainUserId = itemView.findViewById(R.id.key_list_item_name); - mMainUserIdRest = itemView.findViewById(R.id.key_list_item_email); - mStatus = itemView.findViewById(R.id.key_list_item_status_icon); - mSlinger = itemView.findViewById(R.id.key_list_item_slinger_view); - mSlingerButton = itemView.findViewById(R.id.key_list_item_slinger_button); - mCreationDate = itemView.findViewById(R.id.key_list_item_creation); - mTrustIdIcon = itemView.findViewById(R.id.key_list_item_tid_icon); - - itemView.setClickable(true); - itemView.setLongClickable(true); - itemView.setOnClickListener(this); - itemView.setOnLongClickListener(this); - - mSlingerButton.setClickable(true); - mSlingerButton.setOnClickListener(this); - } - - void bindKey(KeyListCursor keyItem, Highlighter highlighter) { - itemView.setSelected(isSelected(getAdapterPosition())); - Context context = itemView.getContext(); - - { // set name and stuff, common to both key types - String name = keyItem.getName(); - String email = keyItem.getEmail(); - if (name == null) { - if (email != null) { - mMainUserId.setText(highlighter.highlight(email)); - mMainUserIdRest.setVisibility(View.GONE); - } else { - mMainUserId.setText(R.string.user_id_no_name); - } - } else { - mMainUserId.setText(highlighter.highlight(name)); - if (email != null) { - mMainUserIdRest.setText(highlighter.highlight(email)); - mMainUserIdRest.setVisibility(View.VISIBLE); - } else { - mMainUserIdRest.setVisibility(View.GONE); - } - } - } - - { // set edit button and status, specific by key type. Note: order is important! - int textColor; - if (keyItem.isRevoked()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - null, - KeyFormattingUtils.State.REVOKED, - R.color.key_flag_gray - ); - - mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (keyItem.isExpired()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - null, - KeyFormattingUtils.State.EXPIRED, - R.color.key_flag_gray - ); - - mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (!keyItem.isSecure()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - null, - KeyFormattingUtils.State.INSECURE, - R.color.key_flag_gray - ); - - mStatus.setVisibility(View.VISIBLE); - mSlinger.setVisibility(View.GONE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (keyItem.isSecret()) { - mStatus.setVisibility(View.GONE); - if (mSlingerButton.hasOnClickListeners()) { - mSlingerButton.setColorFilter( - FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText), - PorterDuff.Mode.SRC_IN - ); - - mSlinger.setVisibility(View.VISIBLE); - } else { - mSlinger.setVisibility(View.GONE); - } - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } else { - // this is a public key - show if it's verified - if (keyItem.isVerified()) { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - KeyFormattingUtils.State.VERIFIED - ); - - mStatus.setVisibility(View.VISIBLE); - } else { - KeyFormattingUtils.setStatusImage( - context, - mStatus, - KeyFormattingUtils.State.UNVERIFIED - ); - - mStatus.setVisibility(View.VISIBLE); - } - mSlinger.setVisibility(View.GONE); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - mMainUserId.setTextColor(textColor); - mMainUserIdRest.setTextColor(textColor); - - if (keyItem.hasDuplicate() || keyItem.isSecret()) { - mCreationDate.setText(getSecretKeyReadableTime(context, keyItem)); - mCreationDate.setTextColor(textColor); - mCreationDate.setVisibility(View.VISIBLE); - } else { - mCreationDate.setVisibility(View.GONE); - } - } - - { // set icons - List packageNames = keyItem.getAutocryptPeerIdPackages(); - - if (!keyItem.isSecret() && !packageNames.isEmpty()) { - String packageName = packageNames.get(0); - Drawable drawable = getDrawableForPackageName(packageName); - if (drawable != null) { - mTrustIdIcon.setImageDrawable(drawable); - mTrustIdIcon.setVisibility(View.VISIBLE); - } else { - mTrustIdIcon.setVisibility(View.GONE); - } - } else { - mTrustIdIcon.setVisibility(View.GONE); - } - } - } - - @NonNull - private String getSecretKeyReadableTime(Context context, KeyListCursor keyItem) { - long creationMillis = keyItem.getCreationTime(); - - boolean allowRelativeTimestamp = keyItem.hasDuplicate(); - if (allowRelativeTimestamp) { - long creationAgeMillis = System.currentTimeMillis() - creationMillis; - if (creationAgeMillis < JUST_NOW_TIMESPAN) { - return context.getString(R.string.label_key_created_just_now); - } - } - - String dateTime = DateUtils.formatDateTime(context, - creationMillis, - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - return context.getString(R.string.label_key_created, dateTime); - } - - @Override - public void onClick(View v) { - int pos = getAdapterPosition(); - switch (v.getId()) { - case R.id.key_list_item_slinger_button: - if (mListener != null) { - mListener.onSlingerButtonClicked(getItemId()); - } - break; - - default: - if (getSelectedCount() == 0) { - if (mListener != null) { - mListener.onKeyItemClicked(getItemId()); - } - } else { - if (isSelected(pos)) { - deselectPosition(pos); - } else { - selectPosition(pos); - } - - if (mListener != null) { - mListener.onSelectionStateChanged(getSelectedCount()); - } - } - break; - } - - } - - @Override - public boolean onLongClick(View v) { - System.out.println("Long Click!"); - if (getSelectedCount() == 0) { - selectPosition(getAdapterPosition()); - - if (mListener != null) { - mListener.onSelectionStateChanged(getSelectedCount()); - } - return true; - } - - return false; - } - } - - static class KeyHeaderViewHolder extends SectionCursorAdapter.ViewHolder { - private TextView mText1; - - public KeyHeaderViewHolder(View itemView) { - super(itemView); - mText1 = itemView.findViewById(android.R.id.text1); - } - - public void bind(String title) { - mText1.setText(title); - } - } - - public static class KeyListCursor extends CursorAdapter.KeyCursor { - public static final String ORDER = KeychainContract.KeyRings.HAS_ANY_SECRET - + " DESC, " + KeychainContract.KeyRings.USER_ID + " COLLATE NOCASE ASC"; - - public static final String[] PROJECTION; - - static { - ArrayList arr = new ArrayList<>(); - arr.addAll(Arrays.asList(KeyCursor.PROJECTION)); - arr.addAll(Arrays.asList( - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.HAS_ENCRYPT, - KeychainContract.KeyRings.API_KNOWN_TO_PACKAGE_NAMES - )); - - PROJECTION = arr.toArray(new String[arr.size()]); - } - - public static KeyListCursor wrap(Cursor cursor) { - if (cursor != null) { - return new KeyListCursor(cursor); - } else { - return null; - } - } - - private KeyListCursor(Cursor cursor) { - super(cursor); - } - - public boolean hasEncrypt() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT); - return getInt(index) != 0; - } - - public byte[] getRawFingerprint() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.FINGERPRINT); - return getBlob(index); - } - - public String getFingerprint() { - return KeyFormattingUtils.convertFingerprintToHex(getRawFingerprint()); - } - - public boolean isSecret() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ANY_SECRET); - return getInt(index) != 0; - } - - public boolean isVerified() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.VERIFIED); - return getInt(index) > 0; - } - - public List getAutocryptPeerIdPackages() { - int index = getColumnIndexOrThrow(KeyRings.API_KNOWN_TO_PACKAGE_NAMES); - String packageNames = getString(index); - if (packageNames == null) { - return Collections.EMPTY_LIST; - } - return Arrays.asList(packageNames.split(",")); - } - } - - public interface KeyListListener { - void onKeyDummyItemClicked(); - - void onKeyItemClicked(long masterKeyId); - - void onSlingerButtonClicked(long masterKeyId); - - void onSelectionStateChanged(int selectedCount); - } - - private HashMap appIconCache = new HashMap<>(); - - private Drawable getDrawableForPackageName(String packageName) { - if (appIconCache.containsKey(packageName)) { - return appIconCache.get(packageName); - } - - PackageManager pm = getContext().getPackageManager(); - try { - ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); - - Drawable appIcon = pm.getApplicationIcon(ai); - appIconCache.put(packageName, appIcon); - - return appIcon; - } catch (PackageManager.NameNotFoundException e) { - return null; - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java deleted file mode 100644 index 4af5faf82..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.util.adapter; - -import android.content.Context; -import android.support.v4.util.SparseArrayCompat; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.view.ViewGroup; - -import com.tonicartos.superslim.LayoutManager; - -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter.SimpleCursor; -import timber.log.Timber; - - -/** - * @param section type. - * @param the view holder extending {@code BaseViewHolder} that is bound to the cursor data. - * @param the view holder extending {@code BaseViewHolder<>} that is bound to the section data. - */ -public abstract class SectionCursorAdapter extends CursorAdapter { - - public static final String TAG = "SectionCursorAdapter"; - - private static final short VIEW_TYPE_ITEM = 0x1; - private static final short VIEW_TYPE_SECTION = 0x2; - - private SparseArrayCompat mSectionMap = new SparseArrayCompat<>(); - private Comparator mSectionComparator; - - public SectionCursorAdapter(Context context, C cursor, int flags) { - this(context, cursor, flags, new Comparator() { - @Override - public boolean equal(T obj1, T obj2) { - return (obj1 == null) ? - obj2 == null : obj1.equals(obj2); - } - }); - } - - public SectionCursorAdapter(Context context, C cursor, int flags, Comparator comparator) { - super(context, cursor, flags); - setSectionComparator(comparator); - } - - @Override - public void onContentChanged() { - if (hasValidData()) { - buildSections(); - } else { - mSectionMap.clear(); - } - - super.onContentChanged(); - } - - /** - * Assign a comparator which will be used to check whether - * a section is contained in the list of sections. The default implementation - * will check for null pointers and compare sections using the {@link #equals(Object)} method. - * @param comparator The comparator to compare section objects. - */ - public void setSectionComparator(Comparator comparator) { - this.mSectionComparator = comparator; - buildSections(); - } - - /** - * If the adapter's cursor is not null then this method will call buildSections(Cursor cursor). - */ - private void buildSections() { - if (hasValidData()) { - moveCursor(-1); - try { - mSectionMap.clear(); - appendSections(getCursor()); - } catch (IllegalStateException e) { - Timber.e(e, "Couldn't build sections. Perhaps you're moving the cursor" + - "in #getSectionFromCursor(Cursor)?"); - swapCursor(null); - - mSectionMap.clear(); - } - } - } - - private void appendSections(C cursor) throws IllegalStateException { - int cursorPosition = 0; - while(hasValidData() && cursor.moveToNext()) { - T section = getSectionFromCursor(cursor); - if (cursor.getPosition() != cursorPosition) { - throw new IllegalStateException("Do not move the cursor's position in getSectionFromCursor."); - } - if (!hasSection(section)) { - mSectionMap.append(cursorPosition + mSectionMap.size(), section); - } - cursorPosition++; - } - } - - public boolean hasSection(T section) { - for(int i = 0; i < mSectionMap.size(); i++) { - T obj = mSectionMap.valueAt(i); - if(mSectionComparator.equal(obj, section)) - return true; - } - - return false; - } - - /** - * The object which is return will determine what section this cursor position will be in. - * @return the section from the cursor at its current position. - * This object will be passed to newSectionView and bindSectionView. - */ - protected abstract T getSectionFromCursor(C cursor) throws IllegalStateException; - - /** - * Return the id of the item represented by the row the cursor - * is currently moved to. - * @param section The section item to get the id from - * @return The id of the dataset - */ - public long getIdFromSection(T section) { - return section != null ? section.hashCode() : 0L; - } - - @Override - public int getItemCount() { - return super.getItemCount() + mSectionMap.size(); - } - - @Override - public final long getItemId(int listPosition) { - int index = mSectionMap.indexOfKey(listPosition); - if (index < 0) { - int cursorPosition = getCursorPositionWithoutSections(listPosition); - return super.getItemId(cursorPosition); - } else { - T section = mSectionMap.valueAt(index); - return getIdFromSection(section); - } - } - - /** - * @param listPosition the position of the current item in the list with mSectionMap included - * @return Whether or not the listPosition points to a section. - */ - public boolean isSection(int listPosition) { - return mSectionMap.indexOfKey(listPosition) >= 0; - } - - /** - * This will map a position in the list adapter (which includes mSectionMap) to a position in - * the cursor (which does not contain mSectionMap). - * - * @param listPosition the position of the current item in the list with mSectionMap included - * @return the correct position to use with the cursor - */ - public int getCursorPositionWithoutSections(int listPosition) { - if (mSectionMap.size() == 0) { - return listPosition; - } else if (!isSection(listPosition)) { - int sectionIndex = getSectionForPosition(listPosition); - if (isListPositionBeforeFirstSection(listPosition, sectionIndex)) { - return listPosition; - } else { - return listPosition - (sectionIndex + 1); - } - } else { - return -1; - } - } - - public int getListPosition(int cursorPosition) { - for(int i = 0; i < mSectionMap.size(); i++) { - int sectionIndex = mSectionMap.keyAt(i); - if (sectionIndex > cursorPosition) { - return cursorPosition; - } - - cursorPosition +=1; - } - - return cursorPosition; - } - - /** - * Given the list position of an item in the adapter, returns the - * adapter position of the first item of the section the given item belongs to. - * @param listPosition The absolute list position. - * @return The position of the first item of the section. - */ - public int getFirstSectionPosition(int listPosition) { - int start = 0; - for(int i = 0; i <= listPosition; i++) { - if(isSection(i)) { - start = i; - } - } - - return start; - } - - - public int getSectionForPosition(int listPosition) { - boolean isSection = false; - int numPrecedingSections = 0; - for (int i = 0; i < mSectionMap.size(); i++) { - int sectionPosition = mSectionMap.keyAt(i); - - if (listPosition > sectionPosition) { - numPrecedingSections++; - } else if (listPosition == sectionPosition) { - isSection = true; - } else { - break; - } - } - - return isSection ? numPrecedingSections : Math.max(numPrecedingSections - 1, 0); - } - - private boolean isListPositionBeforeFirstSection(int listPosition, int sectionIndex) { - boolean hasSections = mSectionMap != null && mSectionMap.size() > 0; - return sectionIndex == 0 && hasSections && listPosition < mSectionMap.keyAt(0); - } - - @Override - public final int getItemViewType(int listPosition) { - int sectionIndex = mSectionMap.indexOfKey(listPosition); - if(sectionIndex < 0) { - int cursorPosition = getCursorPositionWithoutSections(listPosition); - return (getSectionItemViewType(cursorPosition) << 16) | VIEW_TYPE_ITEM; - } else { - return (getSectionHeaderViewType(sectionIndex) << 16) | VIEW_TYPE_SECTION; - } - } - - protected short getSectionHeaderViewType(int sectionIndex) { - return 0; - } - - protected short getSectionItemViewType(int position) { - return 0; - } - - @Override - @SuppressWarnings("unchecked") - public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - LayoutManager.LayoutParams layoutParams = LayoutManager.LayoutParams - .from(holder.itemView.getLayoutParams()); - - // assign first position of section to each item - layoutParams.setFirstPosition(getFirstSectionPosition(position)); - - int viewType = holder.getItemViewType() & 0xFF; - switch (viewType) { - case VIEW_TYPE_ITEM : - moveCursorOrThrow(getCursorPositionWithoutSections(position)); - onBindItemViewHolder((VH) holder, getCursor()); - - layoutParams.isHeader = false; - break; - - case VIEW_TYPE_SECTION: - T section = mSectionMap.get(position); - onBindSectionViewHolder((SH) holder, section); - - layoutParams.isHeader = true; - break; - } - - holder.itemView.setLayoutParams(layoutParams); - } - - @Override - public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType & 0xFF) { - case VIEW_TYPE_SECTION: - return onCreateSectionViewHolder(parent, viewType >> 16); - - case VIEW_TYPE_ITEM: - return onCreateItemViewHolder(parent, viewType >> 16); - - default: - return null; - } - } - - protected abstract SH onCreateSectionViewHolder(ViewGroup parent, int viewType); - protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType); - - protected abstract void onBindSectionViewHolder(SH holder, T section); - protected abstract void onBindItemViewHolder(VH holder, C cursor); - - public interface Comparator { - boolean equal(T obj1, T obj2); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - public ViewHolder(View itemView) { - super(itemView); - } - - /** - * Returns the view type assigned in - * {@link SectionCursorAdapter#getSectionHeaderViewType(int)} or - * {@link SectionCursorAdapter#getSectionItemViewType(int)} - * - * Note that a call to {@link #getItemViewType()} will return a value that contains - * internal stuff necessary to distinguish sections from items. - * @return The view type you set. - */ - public short getItemViewTypeWithoutSections(){ - return (short) (getItemViewType() >> 16); - } - } -} - diff --git a/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml b/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml index 32d726ac1..d76e28dbb 100644 --- a/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml +++ b/OpenKeychain/src/main/res/drawable-v21/list_item_ripple.xml @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/key_list_fragment.xml b/OpenKeychain/src/main/res/layout/key_list_fragment.xml index d53afb940..3c1572a82 100644 --- a/OpenKeychain/src/main/res/layout/key_list_fragment.xml +++ b/OpenKeychain/src/main/res/layout/key_list_fragment.xml @@ -34,7 +34,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - @@ -43,18 +43,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" - android:paddingBottom="72dp" - android:paddingLeft="16dp" - android:paddingRight="32dp" - android:paddingStart="16dp" /> - - + android:paddingBottom="72dp" /> @@ -149,6 +138,21 @@ + + diff --git a/OpenKeychain/src/main/res/layout/key_list_header_public.xml b/OpenKeychain/src/main/res/layout/key_list_header_public.xml index 97fb67984..6391a3bb7 100644 --- a/OpenKeychain/src/main/res/layout/key_list_header_public.xml +++ b/OpenKeychain/src/main/res/layout/key_list_header_public.xml @@ -1,16 +1,12 @@ + android:paddingRight="12dp" + android:paddingLeft="12dp"> - - - - - - - - Date: Wed, 20 Jun 2018 01:21:18 +0200 Subject: [PATCH 019/124] extract deletion of keys from ContentProvider --- .../provider/KeyWritableRepository.java | 6 +++- .../keychain/provider/KeychainProvider.java | 35 ++----------------- .../keychain/KeyRingsPublic.sq | 4 +++ 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index f860a53a1..a32fdacb0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -38,6 +38,7 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.util.LongSparseArray; import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -593,7 +594,10 @@ public class KeyWritableRepository extends KeyRepository { return false; } autocryptPeerDao.deleteByMasterKeyId(masterKeyId); - int deletedRows = contentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); + + DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db); + deleteStatement.bind(masterKeyId); + int deletedRows = deleteStatement.executeUpdateDelete(); databaseNotifyManager.notifyKeyChange(masterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 708bfd649..045e8ccef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -634,39 +634,8 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe * {@inheritDoc} */ @Override - public int delete(Uri uri, String additionalSelection, String[] selectionArgs) { - Timber.v("delete(uri=" + uri + ")"); - - final SupportSQLiteDatabase db = getDb().getWritableDatabase(); - - int count; - final int match = mUriMatcher.match(uri); - - ContentResolver contentResolver = getContext().getContentResolver(); - switch (match) { - // dangerous - case KEY_RINGS_UNIFIED: { - count = db.delete(Tables.KEY_RINGS_PUBLIC, null, null); - break; - } - - case KEY_RING_PUBLIC: { - @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above - String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); - if (!TextUtils.isEmpty(additionalSelection)) { - selection += " AND (" + additionalSelection + ")"; - } - // corresponding keys and userIds are deleted by ON DELETE CASCADE - count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs); - break; - } - - default: { - throw new UnsupportedOperationException("Unknown uri: " + uri); - } - } - - return count; + public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { + throw new UnsupportedOperationException(); } /** diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq index e4df4e133..95debd60f 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq @@ -7,3 +7,7 @@ selectByMasterKeyId: SELECT * FROM keyrings_public WHERE master_key_id = ?; + +deleteByMasterKeyId: +DELETE FROM keyrings_public + WHERE master_key_id = ?; \ No newline at end of file From 377bf55b70d1b6ba594fdcae4e28f7bb086d54a0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 20 Jun 2018 15:40:47 +0200 Subject: [PATCH 020/124] extract linked id loading from ContentProvider --- .../keychain/model/UserPacket.java | 32 ++++ .../keychain/provider/KeychainContract.java | 10 -- .../keychain/provider/KeychainProvider.java | 18 +-- .../ui/ViewKeyAdvUserIdsFragment.java | 4 +- .../keychain/ui/adapter/IdentityAdapter.java | 24 +-- .../keychain/ui/adapter/UserIdsAdapter.java | 15 +- .../ui/dialog/UserIdInfoDialogFragment.java | 27 ++-- .../ui/keyview/LinkedIdViewFragment.java | 128 ++++----------- .../ui/keyview/loader/IdentityDao.java | 150 ++++++++---------- .../presenter/IdentitiesPresenter.java | 10 +- OpenKeychain/src/main/res/values/strings.xml | 2 - .../org/sufficientlysecure/keychain/Certs.sq | 4 +- .../keychain/UserPackets.sq | 30 +++- 13 files changed, 183 insertions(+), 271 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java new file mode 100644 index 000000000..7a9291a97 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java @@ -0,0 +1,32 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.UserPacketsModel; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; + + +@AutoValue +public abstract class UserPacket implements UserPacketsModel { + public static final Factory FACTORY = new Factory<>(AutoValue_UserPacket::new); + public static final SelectUserIdsByMasterKeyIdMapper USER_ID_MAPPER = + FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new); + public static final SelectUserAttributesByTypeAndMasterKeyIdMapper USER_ATTRIBUTE_MAPPER = + FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new); + + @AutoValue + public static abstract class UserId implements SelectUserIdsByMasterKeyIdModel { + public boolean isVerified() { + Integer verified = verified(); + return verified != null && verified == Certs.VERIFIED_SECRET; + } + } + + @AutoValue + public static abstract class UserAttribute implements SelectUserAttributesByTypeAndMasterKeyIdModel { + public boolean isVerified() { + Integer verified = verified(); + return verified != null && verified == Certs.VERIFIED_SECRET; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 85e79df25..696d6e1d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -109,7 +109,6 @@ public class KeychainContract { public static final String PATH_PUBLIC = "public"; public static final String PATH_USER_IDS = "user_ids"; - public static final String PATH_LINKED_IDS = "linked_ids"; public static final String PATH_KEYS = "keys"; public static final String PATH_CERTS = "certs"; @@ -253,15 +252,6 @@ public class KeychainContract { public static Uri buildUserIdsUri(Uri uri) { return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build(); } - - public static Uri buildLinkedIdsUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_LINKED_IDS).build(); - } - - public static Uri buildLinkedIdsUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_LINKED_IDS).build(); - } - } public static class Certs implements CertsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 045e8ccef..40398f741 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -25,7 +25,6 @@ import java.util.List; import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; @@ -39,7 +38,6 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.model.AutocryptPeer; -import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -64,7 +62,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RING_USER_IDS = 202; private static final int KEY_RING_PUBLIC = 203; private static final int KEY_RING_CERTS = 205; - private static final int KEY_RING_LINKED_IDS = 207; private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; @@ -140,9 +137,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_USER_IDS, KEY_RING_USER_IDS); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_LINKED_IDS, - KEY_RING_LINKED_IDS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_PUBLIC, KEY_RING_PUBLIC); @@ -473,8 +467,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } case KEY_RINGS_USER_IDS: - case KEY_RING_USER_IDS: - case KEY_RING_LINKED_IDS: { + case KEY_RING_USER_IDS: { HashMap projectionMap = new HashMap<>(); projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id"); projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID); @@ -502,15 +495,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK; - if (match == KEY_RING_LINKED_IDS) { - qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " = " - + WrappedUserAttribute.UAT_URI_ATTRIBUTE); - } else { - qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); - } + qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); // If we are searching for a particular keyring's ids, add where - if (match == KEY_RING_USER_IDS || match == KEY_RING_LINKED_IDS) { + if (match == KEY_RING_USER_IDS) { qb.appendWhere(" AND "); qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index a0811e8b5..6e51ee7a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -42,6 +43,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; @@ -179,7 +181,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements private void showUserIdInfo(final int position) { final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); - final int isVerified = mUserIdsAdapter.getIsVerified(position); + final boolean isVerified = mUserIdsAdapter.getIsVerified(position) == Certs.VERIFIED_SECRET; DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java index 5500b1674..860e2bf22 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java @@ -35,12 +35,11 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.linked.UriAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.ViewHolder; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; @@ -158,7 +157,7 @@ public class IdentityAdapter extends RecyclerView.Adapter { public void bind(Context context, LinkedIdInfo info, boolean isSecret) { bindVerified(context, info, isSecret); - UriAttribute uriAttribute = info.getUriAttribute(); + UriAttribute uriAttribute = info.getLinkedAttribute(); bind(context, uriAttribute); } @@ -178,19 +177,12 @@ public class IdentityAdapter extends RecyclerView.Adapter { private void bindVerified(Context context, IdentityInfo info, boolean isSecret) { if (!isSecret) { - switch (info.getVerified()) { - case Certs.VERIFIED_SECRET: - KeyFormattingUtils.setStatusImage(context, vVerified, - null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); - break; - case Certs.VERIFIED_SELF: - KeyFormattingUtils.setStatusImage(context, vVerified, - null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); - break; - default: - KeyFormattingUtils.setStatusImage(context, vVerified, - null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); - break; + if (info.isVerified()) { + KeyFormattingUtils.setStatusImage(context, vVerified, + null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + } else { + KeyFormattingUtils.setStatusImage(context, vVerified, + null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index e48b79f1e..8550a5eca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -17,13 +17,11 @@ package org.sufficientlysecure.keychain.ui.adapter; -import android.app.Activity; + import android.content.Context; import android.database.Cursor; import android.graphics.Typeface; -import android.net.Uri; import android.support.annotation.Nullable; -import android.support.v4.content.CursorLoader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,7 +31,6 @@ import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -184,14 +181,4 @@ public class UserIdsAdapter extends UserAttributesAdapter { public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(R.layout.view_key_adv_user_id_item, null); } - - // don't show revoked user ids, irrelevant for average users - public static final String USER_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; - - public static CursorLoader createLoader(Context context, Uri dataUri) { - Uri baseUri = UserPackets.buildUserIdsUri(dataUri); - return new CursorLoader(context, baseUri, - UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java index db20f7d1f..3d6122a68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/UserIdInfoDialogFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui.dialog; + import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; @@ -24,7 +25,6 @@ import android.os.Bundle; import android.support.v4.app.DialogFragment; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; public class UserIdInfoDialogFragment extends DialogFragment { private static final String ARG_IS_REVOKED = "is_revoked"; @@ -33,11 +33,11 @@ public class UserIdInfoDialogFragment extends DialogFragment { /** * Creates new instance of this dialog fragment */ - public static UserIdInfoDialogFragment newInstance(boolean isRevoked, int isVerified) { + public static UserIdInfoDialogFragment newInstance(boolean isRevoked, boolean isVerified) { UserIdInfoDialogFragment frag = new UserIdInfoDialogFragment(); Bundle args = new Bundle(); args.putBoolean(ARG_IS_REVOKED, isRevoked); - args.putInt(ARG_IS_VERIFIED, isVerified); + args.putBoolean(ARG_IS_VERIFIED, isVerified); frag.setArguments(args); @@ -51,7 +51,7 @@ public class UserIdInfoDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); - int isVerified = getArguments().getInt(ARG_IS_VERIFIED); + boolean isVerified = getArguments().getBoolean(ARG_IS_VERIFIED); boolean isRevoked = getArguments().getBoolean(ARG_IS_REVOKED); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -62,19 +62,12 @@ public class UserIdInfoDialogFragment extends DialogFragment { title = getString(R.string.user_id_info_revoked_title); message = getString(R.string.user_id_info_revoked_text); } else { - switch (isVerified) { - case KeychainContract.Certs.VERIFIED_SECRET: - title = getString(R.string.user_id_info_certified_title); - message = getString(R.string.user_id_info_certified_text); - break; - case KeychainContract.Certs.VERIFIED_SELF: - title = getString(R.string.user_id_info_uncertified_title); - message = getString(R.string.user_id_info_uncertified_text); - break; - default: - title = getString(R.string.user_id_info_invalid_title); - message = getString(R.string.user_id_info_invalid_text); - break; + if (isVerified) { + title = getString(R.string.user_id_info_certified_title); + message = getString(R.string.user_id_info_certified_text); + } else { + title = getString(R.string.user_id_info_uncertified_title); + message = getString(R.string.user_id_info_uncertified_text); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index bf5c0f997..902003d41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -18,15 +18,12 @@ package org.sufficientlysecure.keychain.ui.keyview; -import java.io.IOException; import java.util.Collections; import android.arch.lifecycle.LiveData; import android.content.Context; import android.content.Intent; -import android.database.Cursor; import android.graphics.PorterDuff; -import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @@ -37,10 +34,7 @@ import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.OnBackStackChangedListener; -import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -62,15 +56,13 @@ import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; -import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.keyview.LinkedIdViewFragment.ViewHolder.VerifyState; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -81,14 +73,11 @@ import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import timber.log.Timber; -public class LinkedIdViewFragment extends CryptoOperationFragment implements - LoaderManager.LoaderCallbacks, OnBackStackChangedListener { +public class LinkedIdViewFragment extends CryptoOperationFragment implements OnBackStackChangedListener { - private static final String ARG_DATA_URI = "data_uri"; private static final String ARG_LID_RANK = "rank"; private static final String ARG_IS_SECRET = "verified"; private static final String ARG_MASTER_KEY_ID = "master_key_id"; - private static final int LOADER_ID_LINKED_ID = 1; private long masterKeyId; private boolean isSecret; @@ -98,16 +87,14 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements private AsyncTask taskInProgress; - private Uri dataUri; private ViewHolder viewHolder; private int lidRank; private long certifyKeyId; - public static LinkedIdViewFragment newInstance(Uri dataUri, int rank, boolean isSecret, long masterKeyId) { + public static LinkedIdViewFragment newInstance(long masterKeyId, int rank, boolean isSecret) { LinkedIdViewFragment frag = new LinkedIdViewFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); args.putInt(ARG_LID_RANK, rank); args.putBoolean(ARG_IS_SECRET, isSecret); args.putLong(ARG_MASTER_KEY_ID, masterKeyId); @@ -127,57 +114,27 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements super.onCreate(savedInstanceState); Bundle args = getArguments(); - dataUri = args.getParcelable(ARG_DATA_URI); lidRank = args.getInt(ARG_LID_RANK); isSecret = args.getBoolean(ARG_IS_SECRET); masterKeyId = args.getLong(ARG_MASTER_KEY_ID); - getLoaderManager().initLoader(LOADER_ID_LINKED_ID, null, this); - + IdentityDao identityDao = IdentityDao.getInstance(getContext()); + GenericLiveData uriAttributeLiveData = + new GenericLiveData<>(getContext(), null, () -> identityDao.getLinkedIdInfo(masterKeyId, lidRank)); + uriAttributeLiveData.observe(this, this::onLinkedIdInfoLoaded); } - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_LINKED_ID: - return new CursorLoader(getContext(), dataUri, - UserIdsAdapter.USER_PACKETS_PROJECTION, - Tables.USER_PACKETS + "." + UserPackets.RANK - + " = " + Integer.toString(lidRank), null, null); - default: - return null; + private void onLinkedIdInfoLoaded(LinkedIdInfo linkedIdInfo) { + if (linkedIdInfo == null) { + Timber.e("error loading identity"); + Notify.create(getActivity(), "Error loading linked identity!", + Notify.LENGTH_LONG, Style.ERROR).show(); + finishFragment(); + return; } - } - @Override - public void onLoadFinished(Loader loader, Cursor cursor) { - switch (loader.getId()) { - case LOADER_ID_LINKED_ID: - - // Nothing to load means break if we are *expected* to load - if (!cursor.moveToFirst()) { - // Or just ignore, this is probably some intermediate state during certify - break; - } - - try { - int certStatus = cursor.getInt(UserIdsAdapter.INDEX_VERIFIED); - - byte[] data = cursor.getBlob(UserIdsAdapter.INDEX_ATTRIBUTE_DATA); - UriAttribute linkedId = LinkedAttribute.fromAttributeData(data); - - loadIdentity(linkedId, certStatus); - - } catch (IOException e) { - Timber.e(e, "error parsing identity"); - Notify.create(getActivity(), "Error parsing identity!", - Notify.LENGTH_LONG, Style.ERROR).show(); - finishFragment(); - } - - break; - } + loadIdentity(linkedIdInfo.getLinkedAttribute(), linkedIdInfo.isVerified()); } public void finishFragment() { @@ -188,28 +145,19 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements }); } - private void loadIdentity(UriAttribute linkedId, int certStatus) { + private void loadIdentity(LinkedAttribute linkedId, boolean isVerified) { this.linkedId = linkedId; - if (this.linkedId instanceof LinkedAttribute) { - LinkedResource res = ((LinkedAttribute) this.linkedId).mResource; - linkedResource = (LinkedTokenResource) res; - } + LinkedResource res = ((LinkedAttribute) this.linkedId).mResource; + linkedResource = (LinkedTokenResource) res; if (!isSecret) { - switch (certStatus) { - case Certs.VERIFIED_SECRET: - KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified, - null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); - break; - case Certs.VERIFIED_SELF: - KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified, - null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); - break; - default: - KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified, - null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); - break; + if (isVerified) { + KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified, + null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + } else { + KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified, + null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); } } else { viewHolder.mLinkedIdHolder.vVerified.setImageResource(R.drawable.octo_link_24dp); @@ -219,13 +167,6 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements setShowVerifying(false); - // no resource, nothing further we can do… - if (linkedResource == null) { - viewHolder.vButtonView.setVisibility(View.GONE); - viewHolder.vButtonVerify.setVisibility(View.GONE); - return; - } - if (linkedResource.isViewable()) { viewHolder.vButtonView.setVisibility(View.VISIBLE); viewHolder.vButtonView.setOnClickListener(v -> { @@ -241,11 +182,6 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements } - @Override - public void onLoaderReset(Loader loader) { - - } - static class ViewHolder { private final View vButtonView; private final ViewAnimator vVerifyingContainer; @@ -395,25 +331,29 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.linked_id_view_fragment, null); + View root = inflater.inflate(R.layout.linked_id_view_fragment, superContainer, false); + Context context = getContext(); + if (context == null) { + throw new NullPointerException(); + } viewHolder = new ViewHolder(root); root.setTag(viewHolder); ((ImageView) root.findViewById(R.id.status_icon_verified)) - .setColorFilter(ContextCompat.getColor(getContext(), R.color.android_green_light), + .setColorFilter(ContextCompat.getColor(context, R.color.android_green_light), PorterDuff.Mode.SRC_IN); ((ImageView) root.findViewById(R.id.status_icon_invalid)) - .setColorFilter(ContextCompat.getColor(getContext(), R.color.android_red_light), + .setColorFilter(ContextCompat.getColor(context, R.color.android_red_light), PorterDuff.Mode.SRC_IN); viewHolder.vButtonVerify.setOnClickListener(v -> verifyResource()); viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource()); viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying()); - CertificationDao certificationDao = CertificationDao.getInstance(getContext()); + CertificationDao certificationDao = CertificationDao.getInstance(context); LiveData certDetailsLiveData = new GenericLiveData<>( - getContext(), null, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank)); + context, null, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank)); certDetailsLiveData.observe(this, this::onLoadCertDetails); return root; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index fe6609e5f..f3a39cccf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import android.content.ContentResolver; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -33,63 +33,40 @@ import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import com.google.auto.value.AutoValue; +import com.squareup.sqldelight.SqlDelightQuery; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.UriAttribute; import org.sufficientlysecure.keychain.model.AutocryptPeer; +import org.sufficientlysecure.keychain.model.UserPacket; +import org.sufficientlysecure.keychain.model.UserPacket.UserAttribute; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; import timber.log.Timber; public class IdentityDao { - private static final String[] USER_PACKETS_PROJECTION = new String[]{ - UserPackets._ID, - UserPackets.TYPE, - UserPackets.USER_ID, - UserPackets.ATTRIBUTE_DATA, - UserPackets.RANK, - UserPackets.VERIFIED, - UserPackets.IS_PRIMARY, - UserPackets.IS_REVOKED, - UserPackets.NAME, - UserPackets.EMAIL, - UserPackets.COMMENT, - }; - private static final int INDEX_ID = 0; - private static final int INDEX_TYPE = 1; - private static final int INDEX_USER_ID = 2; - private static final int INDEX_ATTRIBUTE_DATA = 3; - private static final int INDEX_RANK = 4; - private static final int INDEX_VERIFIED = 5; - private static final int INDEX_IS_PRIMARY = 6; - private static final int INDEX_IS_REVOKED = 7; - private static final int INDEX_NAME = 8; - private static final int INDEX_EMAIL = 9; - private static final int INDEX_COMMENT = 10; - - private static final String USER_IDS_WHERE = UserPackets.IS_REVOKED + " = 0"; - - - private final ContentResolver contentResolver; + private final SupportSQLiteDatabase db; private final PackageIconGetter packageIconGetter; private final PackageManager packageManager; private final AutocryptPeerDao autocryptPeerDao; - static IdentityDao getInstance(Context context) { - ContentResolver contentResolver = context.getContentResolver(); + public static IdentityDao getInstance(Context context) { + SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase(); PackageManager packageManager = context.getPackageManager(); PackageIconGetter iconGetter = PackageIconGetter.getInstance(context); AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); - return new IdentityDao(contentResolver, packageManager, iconGetter, autocryptPeerDao); + return new IdentityDao(db, packageManager, iconGetter, autocryptPeerDao); } - private IdentityDao(ContentResolver contentResolver, PackageManager packageManager, PackageIconGetter iconGetter, + private IdentityDao(SupportSQLiteDatabase db, + PackageManager packageManager, PackageIconGetter iconGetter, AutocryptPeerDao autocryptPeerDao) { + this.db = db; this.packageManager = packageManager; - this.contentResolver = contentResolver; this.packageIconGetter = iconGetter; this.autocryptPeerDao = autocryptPeerDao; } @@ -156,73 +133,70 @@ public class IdentityDao { } private void loadLinkedIds(ArrayList identities, long masterKeyId) { - Cursor cursor = contentResolver.query(UserPackets.buildLinkedIdsUri(masterKeyId), - USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null); - if (cursor == null) { - Timber.e("Error loading key items!"); - return; - } - - try { + SqlDelightQuery query = UserPacket.FACTORY.selectUserAttributesByTypeAndMasterKeyId( + (long) WrappedUserAttribute.UAT_URI_ATTRIBUTE, masterKeyId); + try (Cursor cursor = db.query(query)) { while (cursor.moveToNext()) { - int rank = cursor.getInt(INDEX_RANK); - int verified = cursor.getInt(INDEX_VERIFIED); - boolean isPrimary = cursor.getInt(INDEX_IS_PRIMARY) != 0; + UserAttribute userAttribute = UserPacket.USER_ATTRIBUTE_MAPPER.map(cursor); - byte[] data = cursor.getBlob(INDEX_ATTRIBUTE_DATA); - try { - UriAttribute uriAttribute = LinkedAttribute.fromAttributeData(data); - if (uriAttribute instanceof LinkedAttribute) { - LinkedIdInfo identityInfo = LinkedIdInfo.create(rank, verified, isPrimary, uriAttribute); - identities.add(identityInfo); - } - } catch (IOException e) { - Timber.e(e, "Failed parsing uri attribute"); - } + LinkedIdInfo linkedIdInfo = parseLinkedIdInfo(userAttribute); + identities.add(linkedIdInfo); } - } finally { - cursor.close(); } } - private void loadUserIds(ArrayList identities, long masterKeyId) { - Cursor cursor = contentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), - USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null); - if (cursor == null) { - Timber.e("Error loading key items!"); - return; + public LinkedIdInfo getLinkedIdInfo(long masterKeyId, int rank) { + SqlDelightQuery query = UserPacket.FACTORY.selectSpecificUserAttribute( + (long) WrappedUserAttribute.UAT_URI_ATTRIBUTE, masterKeyId, rank); + try (Cursor cursor = db.query(query)) { + if (cursor.moveToFirst()) { + UserAttribute userAttribute = UserPacket.USER_ATTRIBUTE_MAPPER.map(cursor); + + return parseLinkedIdInfo(userAttribute); + } } + return null; + } + @Nullable + private LinkedIdInfo parseLinkedIdInfo(UserAttribute userAttribute) { try { + UriAttribute uriAttribute = LinkedAttribute.fromAttributeData(userAttribute.attribute_data()); + if (uriAttribute instanceof LinkedAttribute) { + return LinkedIdInfo.create(userAttribute.rank(), + userAttribute.isVerified(), userAttribute.is_primary(), (LinkedAttribute) uriAttribute); + } + } catch (IOException e) { + Timber.e(e, "Failed parsing uri attribute"); + } + return null; + } + + private void loadUserIds(ArrayList identities, long masterKeyId) { + SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyId); + try (Cursor cursor = db.query(query)) { while (cursor.moveToNext()) { - int rank = cursor.getInt(INDEX_RANK); - int verified = cursor.getInt(INDEX_VERIFIED); - boolean isPrimary = cursor.getInt(INDEX_IS_PRIMARY) != 0; + UserId userId = UserPacket.USER_ID_MAPPER.map(cursor); - if (!cursor.isNull(INDEX_NAME) || !cursor.isNull(INDEX_EMAIL)) { - String name = cursor.getString(INDEX_NAME); - String email = cursor.getString(INDEX_EMAIL); - String comment = cursor.getString(INDEX_COMMENT); - - IdentityInfo identityInfo = UserIdInfo.create(rank, verified, isPrimary, name, email, comment); + if (userId.name() != null || userId.email() != null) { + IdentityInfo identityInfo = UserIdInfo.create( + userId.rank(), userId.isVerified(), userId.is_primary(), userId.name(), userId.email(), userId.comment()); identities.add(identityInfo); } } - } finally { - cursor.close(); } } public interface IdentityInfo { int getRank(); - int getVerified(); + boolean isVerified(); boolean isPrimary(); } @AutoValue public abstract static class UserIdInfo implements IdentityInfo { public abstract int getRank(); - public abstract int getVerified(); + public abstract boolean isVerified(); public abstract boolean isPrimary(); @Nullable @@ -232,29 +206,29 @@ public class IdentityDao { @Nullable public abstract String getComment(); - static UserIdInfo create(int rank, int verified, boolean isPrimary, String name, String email, + static UserIdInfo create(int rank, boolean isVerified, boolean isPrimary, String name, String email, String comment) { - return new AutoValue_IdentityDao_UserIdInfo(rank, verified, isPrimary, name, email, comment); + return new AutoValue_IdentityDao_UserIdInfo(rank, isVerified, isPrimary, name, email, comment); } } @AutoValue public abstract static class LinkedIdInfo implements IdentityInfo { public abstract int getRank(); - public abstract int getVerified(); + public abstract boolean isVerified(); public abstract boolean isPrimary(); - public abstract UriAttribute getUriAttribute(); + public abstract LinkedAttribute getLinkedAttribute(); - static LinkedIdInfo create(int rank, int verified, boolean isPrimary, UriAttribute uriAttribute) { - return new AutoValue_IdentityDao_LinkedIdInfo(rank, verified, isPrimary, uriAttribute); + static LinkedIdInfo create(int rank, boolean isVerified, boolean isPrimary, LinkedAttribute linkedAttribute) { + return new AutoValue_IdentityDao_LinkedIdInfo(rank, isVerified, isPrimary, linkedAttribute); } } @AutoValue public abstract static class AutocryptPeerInfo implements IdentityInfo { public abstract int getRank(); - public abstract int getVerified(); + public abstract boolean isVerified(); public abstract boolean isPrimary(); public abstract String getIdentity(); @@ -268,13 +242,13 @@ public class IdentityDao { static AutocryptPeerInfo create(UserIdInfo userIdInfo, String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityDao_AutocryptPeerInfo(userIdInfo.getRank(), userIdInfo.getVerified(), + return new AutoValue_IdentityDao_AutocryptPeerInfo(userIdInfo.getRank(), userIdInfo.isVerified(), userIdInfo.isPrimary(), autocryptPeer, packageName, appIcon, userIdInfo, autocryptPeerIntent); } static AutocryptPeerInfo create(String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { return new AutoValue_IdentityDao_AutocryptPeerInfo( - 0, Certs.VERIFIED_SELF, false, autocryptPeer, packageName, appIcon, null, autocryptPeerIntent); + 0, false, false, autocryptPeer, packageName, appIcon, null, autocryptPeerIntent); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java index 566868d34..9bb3815b4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java @@ -18,20 +18,17 @@ package org.sufficientlysecure.keychain.ui.keyview.presenter; -import java.io.IOException; import java.util.List; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.support.annotation.Nullable; import android.view.View; import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; @@ -114,17 +111,14 @@ public class IdentitiesPresenter implements Observer> { } private void showLinkedId(final LinkedIdInfo info) { - Uri dataUri = UserPackets.buildLinkedIdsUri(KeyRings.buildGenericKeyRingUri(masterKeyId)); - LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(dataUri, info.getRank(), isSecret, masterKeyId); + LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(masterKeyId, info.getRank(), isSecret); viewKeyMvpView.switchToFragment(frag, "linked_id"); } private void showUserIdInfo(UserIdInfo info) { if (!isSecret) { - final int isVerified = info.getVerified(); - - UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(false, isVerified); + UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(false, info.isVerified()); viewKeyMvpView.showDialogFragment(dialogFragment, "userIdInfoDialog"); } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 60c8f19d5..1b3a4aa17 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -727,8 +727,6 @@ "This identity has been confirmed by you." "Not confirmed" "This identity has not been confirmed yet. You cannot be sure if the identity really corresponds to a specific person." - "Invalid" - "Something is wrong with this identity!" "No proof from the Internet on this key’s trustworthiness." diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq index 98c284b52..377135d4c 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq @@ -1,10 +1,12 @@ +import java.lang.Integer; + -- TODO implement. this is only here for reference in SQLDelight CREATE TABLE IF NOT EXISTS certs( master_key_id INTEGER NOT NULL, rank INTEGER NOT NULL, key_id_certifier INTEGER NOT NULL, type INTEGER NOT NULL, - verified INTEGER NOT NULL, + verified INTEGER AS Integer NOT NULL, creation INTEGER NOT NULL, data BLOB NOT NULL, PRIMARY KEY(master_key_id, rank, key_id_certifier), diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq index 75b5fe4a1..062869b34 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq @@ -1,15 +1,35 @@ +import java.lang.Integer; + CREATE TABLE IF NOT EXISTS user_packets( - master_key_id INTEGER, + master_key_id INTEGER NOT NULL, + rank INTEGER AS Integer NOT NULL, type INTEGER, user_id TEXT, name TEXT, email TEXT, comment TEXT, attribute_data BLOB, - is_primary INTEGER, - is_revoked INTEGER, - rank INTEGER, + is_primary INTEGER AS Boolean NOT NULL, + is_revoked INTEGER AS Boolean NOT NULL, PRIMARY KEY(master_key_id, rank), FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE -); \ No newline at end of file +); + +selectUserIdsByMasterKeyId: +SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, certs.verified + FROM user_packets + LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) + WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ?; + +selectUserAttributesByTypeAndMasterKeyId: +SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary, is_revoked, certs.verified + FROM user_packets + LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) + WHERE user_packets.type = ? AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ?; + +selectSpecificUserAttribute: +SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary, is_revoked, certs.verified + FROM user_packets + LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) + WHERE user_packets.type = ? AND user_packets.master_key_id = ? AND user_packets.rank = ?; From b4ac6cd33705070fbd834a73d00136ecaa5c1623 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 21 Jun 2018 14:24:52 +0200 Subject: [PATCH 021/124] Split up FlexibleKeyItem, re-add support for dummy item if user has no secret keys --- .../keychain/provider/KeychainDatabase.java | 22 +- .../keychain/ui/KeyListFragment.java | 42 +++- .../ui/adapter/FlexibleKeyDetailsItem.java | 237 ++++++++++++++++++ .../ui/adapter/FlexibleKeyDummyItem.java | 48 ++++ .../ui/adapter/FlexibleKeyHeader.java | 6 +- .../keychain/ui/adapter/FlexibleKeyItem.java | 236 ++--------------- .../ui/adapter/FlexibleKeyItemFactory.java | 5 +- .../src/main/res/layout/key_list_dummy.xml | 3 +- .../src/main/res/layout/key_list_item.xml | 2 - 9 files changed, 359 insertions(+), 242 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDummyItem.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 30192e3cc..e8a6c7472 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -453,12 +453,28 @@ public class KeychainDatabase { } private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;"); - db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;"); + try { + db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;"); + db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;"); + } catch (SQLException e) { + if (Constants.DEBUG) { + Timber.e(e, "Ignoring migration exception, this probably happened before"); + return; + } + throw e; + } } private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;"); + try { + db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;"); + } catch (SQLException e) { + if (Constants.DEBUG) { + Timber.e(e, "Ignoring migration exception, this probably happened before"); + return; + } + throw e; + } } public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { 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 77f51c37f..581a3b206 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -54,7 +54,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener; import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener; import eu.davidea.flexibleadapter.SelectableAdapter.Mode; -import eu.davidea.flexibleadapter.common.FlexibleItemDecoration; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; @@ -67,8 +66,11 @@ import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; @@ -76,7 +78,6 @@ import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; @@ -163,7 +164,7 @@ public class KeyListFragment extends RecyclerFragment selectedPositions = adapter.getSelectedPositions(); long[] keyIds = new long[selectedPositions.size()]; for (int i = 0; i < selectedPositions.size(); i++) { - FlexibleKeyItem selectedItem = adapter.getItem(selectedPositions.get(i)); + FlexibleKeyDetailsItem selectedItem = adapter.getItem(selectedPositions.get(i), FlexibleKeyDetailsItem.class); if (selectedItem != null) { keyIds[i] = selectedItem.keyInfo.master_key_id(); } @@ -174,7 +175,7 @@ public class KeyListFragment extends RecyclerFragment adapter = getAdapter(); for (int position : adapter.getSelectedPositions()) { - FlexibleKeyItem item = adapter.getItem(position); + FlexibleKeyDetailsItem item = adapter.getItem(position, FlexibleKeyDetailsItem.class); if (item != null && item.keyInfo.has_any_secret()) { return true; } @@ -296,8 +297,14 @@ public class KeyListFragment extends RecyclerFragment + implements IFilterable { + public final UnifiedKeyInfo keyInfo; + + FlexibleKeyDetailsItem(UnifiedKeyInfo keyInfo, FlexibleKeyHeader header) { + super(header); + this.keyInfo = keyInfo; + + setSelectable(true); + } + + @Override + public int getLayoutRes() { + return R.layout.key_list_item; + } + + @Override + public FlexibleKeyItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new FlexibleKeyItemViewHolder(view, adapter); + } + + @Override + public void bindViewHolder( + FlexibleAdapter adapter, FlexibleKeyItemViewHolder holder, int position, List payloads) { + String highlightString = adapter.getFilter(String.class); + holder.bind(keyInfo, highlightString); + } + + @Override + public boolean equals(Object o) { + if (o instanceof org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem) { + org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem + other = (org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem) o; + return keyInfo.master_key_id() == other.keyInfo.master_key_id(); + } + return false; + } + + @Override + public int hashCode() { + long masterKeyId = keyInfo.master_key_id(); + return (int) (masterKeyId ^ (masterKeyId >>> 32)); + } + + @Override + public boolean filter(String constraint) { + String uidList = keyInfo.user_id_list(); + return constraint == null || (uidList != null && uidList.contains(constraint)); + } + + class FlexibleKeyItemViewHolder extends FlexibleViewHolder { + private static final long JUST_NOW_THRESHOLD = DateUtils.MINUTE_IN_MILLIS * 5; + + private final TextView vMainUserId; + private final TextView vMainUserIdRest; + private final TextView vCreationDate; + private final ImageView vStatusIcon; + private final ImageView vTrustIdIcon; + + FlexibleKeyItemViewHolder(View itemView, FlexibleAdapter adapter) { + super(itemView, adapter); + + vMainUserId = itemView.findViewById(R.id.key_list_item_name); + vMainUserIdRest = itemView.findViewById(R.id.key_list_item_email); + vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon); + vCreationDate = itemView.findViewById(R.id.key_list_item_creation); + vTrustIdIcon = itemView.findViewById(R.id.key_list_item_tid_icon); + } + + public void bind(UnifiedKeyInfo keyInfo, String highlightString) { + setEnabled(true); + + Context context = itemView.getContext(); + Highlighter highlighter = new Highlighter(context, highlightString); + + { // set name and stuff, common to both key types + if (keyInfo.name() == null) { + if (keyInfo.email() != null) { + vMainUserId.setText(highlighter.highlight(keyInfo.email())); + vMainUserIdRest.setVisibility(View.GONE); + } else { + vMainUserId.setText(R.string.user_id_no_name); + } + } else { + vMainUserId.setText(highlighter.highlight(keyInfo.name())); + // for some reason, this hangs for me + // FlexibleUtils.highlightText(vMainUserId, keyInfo.name(), highlightString); + if (keyInfo.email() != null) { + vMainUserIdRest.setText(highlighter.highlight(keyInfo.email())); + vMainUserIdRest.setVisibility(View.VISIBLE); + } else { + vMainUserIdRest.setVisibility(View.GONE); + } + } + } + + { // set edit button and status, specific by key type. Note: order is important! + int textColor; + if (keyInfo.is_revoked()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + vStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (keyInfo.is_expired()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + vStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (!keyInfo.is_secure()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + null, + KeyFormattingUtils.State.INSECURE, + R.color.key_flag_gray + ); + + vStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (keyInfo.has_any_secret()) { + vStatusIcon.setVisibility(View.GONE); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } else { + // this is a public key - show if it's verified + if (keyInfo.is_verified()) { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + KeyFormattingUtils.State.VERIFIED + ); + + vStatusIcon.setVisibility(View.VISIBLE); + } else { + KeyFormattingUtils.setStatusImage( + context, + vStatusIcon, + KeyFormattingUtils.State.UNVERIFIED + ); + + vStatusIcon.setVisibility(View.VISIBLE); + } + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + vMainUserId.setTextColor(textColor); + vMainUserIdRest.setTextColor(textColor); + + if (keyInfo.has_duplicate() || keyInfo.has_any_secret()) { + vCreationDate.setText(getSecretKeyReadableTime(context, keyInfo)); + vCreationDate.setTextColor(textColor); + vCreationDate.setVisibility(View.VISIBLE); + } else { + vCreationDate.setVisibility(View.GONE); + } + } + + { // set icons + + if (!keyInfo.has_any_secret() && !keyInfo.autocrypt_package_names().isEmpty()) { + String packageName = keyInfo.autocrypt_package_names().get(0); + Drawable drawable = PackageIconGetter.getInstance(context).getDrawableForPackageName(packageName); + if (drawable != null) { + vTrustIdIcon.setImageDrawable(drawable); + vTrustIdIcon.setVisibility(View.VISIBLE); + } else { + vTrustIdIcon.setVisibility(View.GONE); + } + } else { + vTrustIdIcon.setVisibility(View.GONE); + } + } + } + + + @NonNull + private String getSecretKeyReadableTime(Context context, UnifiedKeyInfo keyInfo) { + long creationMillis = keyInfo.creation() * 1000; + + boolean allowRelativeTimestamp = keyInfo.has_duplicate(); + if (allowRelativeTimestamp) { + long creationAgeMillis = System.currentTimeMillis() - creationMillis; + if (creationAgeMillis < JUST_NOW_THRESHOLD) { + return context.getString(R.string.label_key_created_just_now); + } + } + + String dateTime = DateUtils.formatDateTime(context, + creationMillis, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + return context.getString(R.string.label_key_created, dateTime); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDummyItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDummyItem.java new file mode 100644 index 000000000..a2f707983 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDummyItem.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.List; + +import android.view.View; + +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem.FlexibleKeyDummyViewHolder; +import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem; + + +public class FlexibleKeyDummyItem extends FlexibleSectionableKeyItem { + FlexibleKeyDummyItem(FlexibleKeyHeader header) { + super(header); + + setSelectable(false); + } + + @Override + public int getLayoutRes() { + return R.layout.key_list_dummy; + } + + @Override + public boolean equals(Object o) { + return this == o; + } + + @Override + public FlexibleKeyDummyViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new FlexibleKeyDummyViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, FlexibleKeyDummyViewHolder holder, + int position, List payloads) { + } + + class FlexibleKeyDummyViewHolder extends FlexibleViewHolder { + private FlexibleKeyDummyViewHolder(View view, FlexibleAdapter adapter) { + super(view, adapter, true); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java index fda48678f..2c2327e98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyHeader.java @@ -9,18 +9,20 @@ import android.widget.TextView; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractHeaderItem; import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.flexibleadapter.items.IHeader; import eu.davidea.viewholders.FlexibleViewHolder; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyHeader.FlexibleHeaderViewHolder; -public class FlexibleKeyHeader extends AbstractHeaderItem { +public class FlexibleKeyHeader extends FlexibleKeyItem + implements IHeader { private final String sectionTitle; FlexibleKeyHeader(String sectionTitle) { + super(); this.sectionTitle = sectionTitle; setEnabled(false); - setSelectable(false); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java index cd9f8726e..3f4c0bd5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItem.java @@ -1,236 +1,30 @@ package org.sufficientlysecure.keychain.ui.adapter; -import java.util.List; +import android.support.v7.widget.RecyclerView; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import eu.davidea.flexibleadapter.FlexibleAdapter; -import eu.davidea.flexibleadapter.items.AbstractSectionableItem; -import eu.davidea.flexibleadapter.items.IFilterable; -import eu.davidea.flexibleadapter.items.IFlexible; -import eu.davidea.viewholders.FlexibleViewHolder; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleKeyItemViewHolder; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +import eu.davidea.flexibleadapter.items.ISectionable; -public class FlexibleKeyItem extends AbstractSectionableItem - implements IFilterable { - public final UnifiedKeyInfo keyInfo; +public abstract class FlexibleKeyItem extends AbstractFlexibleItem { - FlexibleKeyItem(UnifiedKeyInfo keyInfo, FlexibleKeyHeader header) { - super(header); - this.keyInfo = keyInfo; + public static abstract class FlexibleSectionableKeyItem + extends FlexibleKeyItem implements ISectionable { + FlexibleKeyHeader header; - setSelectable(true); - } - - @Override - public int getLayoutRes() { - return R.layout.key_list_item; - } - - @Override - public FlexibleKeyItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) { - return new FlexibleKeyItemViewHolder(view, adapter); - } - - @Override - public void bindViewHolder( - FlexibleAdapter adapter, FlexibleKeyItemViewHolder holder, int position, List payloads) { - String highlightString = adapter.getFilter(String.class); - holder.bind(keyInfo, highlightString); - } - - @Override - public boolean equals(Object o) { - if (o instanceof FlexibleKeyItem) { - FlexibleKeyItem other = (FlexibleKeyItem) o; - return keyInfo.master_key_id() == other.keyInfo.master_key_id(); - } - return false; - } - - @Override - public int hashCode() { - long masterKeyId = keyInfo.master_key_id(); - return (int) (masterKeyId ^ (masterKeyId >>> 32)); - } - - @Override - public boolean filter(String constraint) { - String uidList = keyInfo.user_id_list(); - return constraint == null || (uidList != null && uidList.contains(constraint)); - } - - public class FlexibleKeyItemViewHolder extends FlexibleViewHolder { - private static final long JUST_NOW_THRESHOLD = DateUtils.MINUTE_IN_MILLIS * 5; - - private final TextView vMainUserId; - private final TextView vMainUserIdRest; - private final TextView vCreationDate; - private final ImageView vStatusIcon; - private final ImageView vTrustIdIcon; - - FlexibleKeyItemViewHolder(View itemView, FlexibleAdapter adapter) { - super(itemView, adapter); - - vMainUserId = itemView.findViewById(R.id.key_list_item_name); - vMainUserIdRest = itemView.findViewById(R.id.key_list_item_email); - vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon); - vCreationDate = itemView.findViewById(R.id.key_list_item_creation); - vTrustIdIcon = itemView.findViewById(R.id.key_list_item_tid_icon); + FlexibleSectionableKeyItem(FlexibleKeyHeader header) { + this.header = header; } - public void bind(UnifiedKeyInfo keyInfo, String highlightString) { - setEnabled(true); - - Context context = itemView.getContext(); - Highlighter highlighter = new Highlighter(context, highlightString); - - { // set name and stuff, common to both key types - if (keyInfo.name() == null) { - if (keyInfo.email() != null) { - vMainUserId.setText(highlighter.highlight(keyInfo.email())); - vMainUserIdRest.setVisibility(View.GONE); - } else { - vMainUserId.setText(R.string.user_id_no_name); - } - } else { - vMainUserId.setText(highlighter.highlight(keyInfo.name())); - // for some reason, this hangs for me - // FlexibleUtils.highlightText(vMainUserId, keyInfo.name(), highlightString); - if (keyInfo.email() != null) { - vMainUserIdRest.setText(highlighter.highlight(keyInfo.email())); - vMainUserIdRest.setVisibility(View.VISIBLE); - } else { - vMainUserIdRest.setVisibility(View.GONE); - } - } - } - - { // set edit button and status, specific by key type. Note: order is important! - int textColor; - if (keyInfo.is_revoked()) { - KeyFormattingUtils.setStatusImage( - context, - vStatusIcon, - null, - KeyFormattingUtils.State.REVOKED, - R.color.key_flag_gray - ); - - vStatusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (keyInfo.is_expired()) { - KeyFormattingUtils.setStatusImage( - context, - vStatusIcon, - null, - KeyFormattingUtils.State.EXPIRED, - R.color.key_flag_gray - ); - - vStatusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (!keyInfo.is_secure()) { - KeyFormattingUtils.setStatusImage( - context, - vStatusIcon, - null, - KeyFormattingUtils.State.INSECURE, - R.color.key_flag_gray - ); - - vStatusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (keyInfo.has_any_secret()) { - vStatusIcon.setVisibility(View.GONE); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } else { - // this is a public key - show if it's verified - if (keyInfo.is_verified()) { - KeyFormattingUtils.setStatusImage( - context, - vStatusIcon, - KeyFormattingUtils.State.VERIFIED - ); - - vStatusIcon.setVisibility(View.VISIBLE); - } else { - KeyFormattingUtils.setStatusImage( - context, - vStatusIcon, - KeyFormattingUtils.State.UNVERIFIED - ); - - vStatusIcon.setVisibility(View.VISIBLE); - } - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - vMainUserId.setTextColor(textColor); - vMainUserIdRest.setTextColor(textColor); - - if (keyInfo.has_duplicate() || keyInfo.has_any_secret()) { - vCreationDate.setText(getSecretKeyReadableTime(context, keyInfo)); - vCreationDate.setTextColor(textColor); - vCreationDate.setVisibility(View.VISIBLE); - } else { - vCreationDate.setVisibility(View.GONE); - } - } - - { // set icons - - if (!keyInfo.has_any_secret() && !keyInfo.autocrypt_package_names().isEmpty()) { - String packageName = keyInfo.autocrypt_package_names().get(0); - Drawable drawable = PackageIconGetter.getInstance(context).getDrawableForPackageName(packageName); - if (drawable != null) { - vTrustIdIcon.setImageDrawable(drawable); - vTrustIdIcon.setVisibility(View.VISIBLE); - } else { - vTrustIdIcon.setVisibility(View.GONE); - } - } else { - vTrustIdIcon.setVisibility(View.GONE); - } - } + @Override + public FlexibleKeyHeader getHeader() { + return header; } - - @NonNull - private String getSecretKeyReadableTime(Context context, UnifiedKeyInfo keyInfo) { - long creationMillis = keyInfo.creation() * 1000; - - boolean allowRelativeTimestamp = keyInfo.has_duplicate(); - if (allowRelativeTimestamp) { - long creationAgeMillis = System.currentTimeMillis() - creationMillis; - if (creationAgeMillis < JUST_NOW_THRESHOLD) { - return context.getString(R.string.label_key_created_just_now); - } - } - - String dateTime = DateUtils.formatDateTime(context, - creationMillis, - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - return context.getString(R.string.label_key_created, dateTime); + @Override + public void setHeader(FlexibleKeyHeader header) { + this.header = header; } } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java index 7b89a64c1..abf5007a8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java @@ -27,9 +27,12 @@ public class FlexibleKeyItemFactory { if (unifiedKeyInfos == null) { return result; } + if (unifiedKeyInfos.isEmpty() || !unifiedKeyInfos.get(0).has_any_secret()) { + result.add(new FlexibleKeyDummyItem(myKeysHeader)); + } for (UnifiedKeyInfo unifiedKeyInfo : unifiedKeyInfos) { FlexibleKeyHeader header = getFlexibleKeyHeader(unifiedKeyInfo); - FlexibleKeyItem flexibleKeyItem = new FlexibleKeyItem(unifiedKeyInfo, header); + FlexibleKeyItem flexibleKeyItem = new FlexibleKeyDetailsItem(unifiedKeyInfo, header); result.add(flexibleKeyItem); } return result; diff --git a/OpenKeychain/src/main/res/layout/key_list_dummy.xml b/OpenKeychain/src/main/res/layout/key_list_dummy.xml index 69a477912..41477f1fd 100644 --- a/OpenKeychain/src/main/res/layout/key_list_dummy.xml +++ b/OpenKeychain/src/main/res/layout/key_list_dummy.xml @@ -5,7 +5,8 @@ android:minHeight="?android:attr/listPreferredItemHeight" android:gravity="center_vertical" android:orientation="horizontal" - android:focusable="true" + android:paddingLeft="12dp" + android:paddingRight="12dp" android:background="?android:selectableItemBackground"> From 729121c13c49e0acbf014316afd7490fd3f4df78 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 21 Jun 2018 15:57:10 +0200 Subject: [PATCH 022/124] add comment to buildfiles about android debug-db for convenience --- OpenKeychain/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index cf5fdd6ed..4e9bb106d 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -103,6 +103,9 @@ dependencies { annotationProcessor "android.arch.lifecycle:compiler:1.0.0" compile "android.arch.persistence:db-framework:1.0.0" + + // for debugging the db. don't enable by default, this will expose the database no the network! + // debugImplementation 'com.amitshekhar.android:debug-db:1.0.3' } // Output of ./gradlew -q calculateChecksums From 587b8b4cc96375051e716bdcaaca2890e83d3231 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 21 Jun 2018 19:15:52 +0200 Subject: [PATCH 023/124] Don't use backstack for back button handling in MainActivity --- .../keychain/ui/KeyListFragment.java | 5 -- .../keychain/ui/MainActivity.java | 64 +++++-------------- 2 files changed, 15 insertions(+), 54 deletions(-) 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 581a3b206..7f28946b1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -238,9 +238,6 @@ public class KeyListFragment extends RecyclerFragment Date: Fri, 22 Jun 2018 10:53:04 +0200 Subject: [PATCH 024/124] introduce AbstractDao, fix import of keys (missing delete in KeychainProvider) --- .../keychain/provider/AbstractDao.java | 27 ++++++++++++ ...piDataAccessObject.java => ApiAppDao.java} | 33 ++++++++------- .../keychain/provider/AutocryptPeerDao.java | 41 ++++++++----------- .../keychain/provider/KeyMetadataDao.java | 26 +++++------- .../keychain/provider/KeyRepository.java | 21 +++++----- .../provider/KeyWritableRepository.java | 24 ++++++----- .../SimpleContentResolverInterface.java | 2 +- .../keychain/remote/ApiPermissionHelper.java | 10 ++--- .../remote/KeychainExternalProvider.java | 4 +- .../keychain/remote/OpenPgpService.java | 10 ++--- .../remote/PackageUninstallReceiver.java | 6 +-- .../remote/SshAuthenticationService.java | 10 ++--- .../remote/ui/AppSettingsActivity.java | 10 ++--- .../AppSettingsAllowedKeysListFragment.java | 10 ++--- .../keychain/remote/ui/AppsListFragment.java | 8 ++-- .../remote/ui/RemoteRegisterPresenter.java | 8 ++-- .../ui/RequestKeyPermissionPresenter.java | 16 ++++---- .../ui/SelectSignKeyIdListFragment.java | 8 ++-- ...RemoteSelectAuthenticationKeyActivity.java | 6 +-- .../RemoteSelectIdentityKeyPresenter.java | 14 +++---- .../remote/KeychainExternalProviderTest.java | 16 ++++---- 21 files changed, 166 insertions(+), 144 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/{ApiDataAccessObject.java => ApiAppDao.java} (77%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java new file mode 100644 index 000000000..89e084989 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java @@ -0,0 +1,27 @@ +package org.sufficientlysecure.keychain.provider; + + +import android.arch.persistence.db.SupportSQLiteDatabase; + + +class AbstractDao { + private final KeychainDatabase db; + private final DatabaseNotifyManager databaseNotifyManager; + + AbstractDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) { + this.db = db; + this.databaseNotifyManager = databaseNotifyManager; + } + + SupportSQLiteDatabase getReadableDb() { + return db.getReadableDatabase(); + } + + SupportSQLiteDatabase getWritableDb() { + return db.getWritableDatabase(); + } + + DatabaseNotifyManager getDatabaseNotifyManager() { + return databaseNotifyManager; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java similarity index 77% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java index 22f1b9cc8..10129b3a5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java @@ -23,7 +23,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; import android.database.Cursor; @@ -36,16 +35,20 @@ import org.sufficientlysecure.keychain.model.ApiAllowedKey; import org.sufficientlysecure.keychain.model.ApiApp; -public class ApiDataAccessObject { - private final SupportSQLiteDatabase db; - - public ApiDataAccessObject(Context context) { +public class ApiAppDao extends AbstractDao { + public static ApiAppDao getInstance(Context context) { KeychainDatabase keychainDatabase = new KeychainDatabase(context); - db = keychainDatabase.getWritableDatabase(); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); + + return new ApiAppDao(keychainDatabase, databaseNotifyManager); + } + + private ApiAppDao(KeychainDatabase keychainDatabase, DatabaseNotifyManager databaseNotifyManager) { + super(keychainDatabase, databaseNotifyManager); } public ApiApp getApiApp(String packageName) { - try (Cursor cursor = db.query(ApiApp.FACTORY.selectByPackageName(packageName))) { + try (Cursor cursor = getReadableDb().query(ApiApp.FACTORY.selectByPackageName(packageName))) { if (cursor.moveToFirst()) { return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor); } @@ -54,7 +57,7 @@ public class ApiDataAccessObject { } public byte[] getApiAppCertificate(String packageName) { - try (Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName))) { + try (Cursor cursor = getReadableDb().query(ApiApp.FACTORY.getCertificate(packageName))) { if (cursor.moveToFirst()) { return ApiApp.FACTORY.getCertificateMapper().map(cursor); } @@ -63,13 +66,13 @@ public class ApiDataAccessObject { } public void insertApiApp(ApiApp apiApp) { - InsertApiApp statement = new ApiAppsModel.InsertApiApp(db); + InsertApiApp statement = new ApiAppsModel.InsertApiApp(getWritableDb()); statement.bind(apiApp.package_name(), apiApp.package_signature()); statement.execute(); } public void deleteApiApp(String packageName) { - DeleteByPackageName deleteByPackageName = new DeleteByPackageName(db); + DeleteByPackageName deleteByPackageName = new DeleteByPackageName(getWritableDb()); deleteByPackageName.bind(packageName); deleteByPackageName.executeUpdateDelete(); } @@ -77,7 +80,7 @@ public class ApiDataAccessObject { public HashSet getAllowedKeyIdsForApp(String packageName) { SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName); HashSet keyIds = new HashSet<>(); - try (Cursor cursor = db.query(allowedKeys)) { + try (Cursor cursor = getReadableDb().query(allowedKeys)) { while (cursor.moveToNext()) { long allowedKeyId = ApiAllowedKey.FACTORY.getAllowedKeysMapper().map(cursor); keyIds.add(allowedKeyId); @@ -87,11 +90,11 @@ public class ApiDataAccessObject { } public void saveAllowedKeyIdsForApp(String packageName, Set allowedKeyIds) { - ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(db); + ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(getWritableDb()); deleteByPackageName.bind(packageName); deleteByPackageName.executeUpdateDelete(); - InsertAllowedKey statement = new InsertAllowedKey(db); + InsertAllowedKey statement = new InsertAllowedKey(getWritableDb()); for (Long keyId : allowedKeyIds) { statement.bind(packageName, keyId); statement.execute(); @@ -99,7 +102,7 @@ public class ApiDataAccessObject { } public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) { - InsertAllowedKey statement = new InsertAllowedKey(db); + InsertAllowedKey statement = new InsertAllowedKey(getWritableDb()); statement.bind(packageName, allowedKeyId); statement.execute(); } @@ -108,7 +111,7 @@ public class ApiDataAccessObject { SqlDelightQuery query = ApiApp.FACTORY.selectAll(); ArrayList result = new ArrayList<>(); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { while (cursor.moveToNext()) { ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor); result.add(apiApp); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java index bdf79f6a7..3e9f6e2dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; import android.database.Cursor; import android.support.annotation.Nullable; @@ -39,25 +38,21 @@ import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; -public class AutocryptPeerDao { - private final SupportSQLiteDatabase db; - private final DatabaseNotifyManager databaseNotifyManager; - +public class AutocryptPeerDao extends AbstractDao { public static AutocryptPeerDao getInstance(Context context) { KeychainDatabase keychainDatabase = new KeychainDatabase(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - return new AutocryptPeerDao(keychainDatabase.getWritableDatabase(), databaseNotifyManager); + return new AutocryptPeerDao(keychainDatabase, databaseNotifyManager); } - private AutocryptPeerDao(SupportSQLiteDatabase writableDatabase, DatabaseNotifyManager databaseNotifyManager) { - this.db = writableDatabase; - this.databaseNotifyManager = databaseNotifyManager; + private AutocryptPeerDao(KeychainDatabase database, DatabaseNotifyManager databaseNotifyManager) { + super(database, databaseNotifyManager); } public Long getMasterKeyIdForAutocryptPeer(String autocryptId) { SqlDelightQuery query = AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifier(autocryptId); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToFirst()) { return AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifierMapper().map(cursor); } @@ -74,10 +69,10 @@ public class AutocryptPeerDao { return null; } - public List getAutocryptPeers(String packageName, String... autocryptId) { + private List getAutocryptPeers(String packageName, String... autocryptId) { ArrayList result = new ArrayList<>(autocryptId.length); SqlDelightQuery query = AutocryptPeer.FACTORY.selectByIdentifiers(packageName, autocryptId); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToNext()) { AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor); result.add(autocryptPeer); @@ -89,7 +84,7 @@ public class AutocryptPeerDao { public List getAutocryptKeyStatus(String packageName, String[] autocryptIds) { ArrayList result = new ArrayList<>(autocryptIds.length); SqlDelightQuery query = AutocryptPeer.FACTORY.selectAutocryptKeyStatus(packageName, autocryptIds, System.currentTimeMillis()); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToNext()) { AutocryptKeyStatus autocryptPeer = AutocryptPeer.KEY_STATUS_MAPPER.map(cursor); result.add(autocryptPeer); @@ -99,12 +94,12 @@ public class AutocryptPeerDao { } public void insertOrUpdateLastSeen(String packageName, String autocryptId, Date date) { - UpdateLastSeen updateStatement = new UpdateLastSeen(db, AutocryptPeer.FACTORY); + UpdateLastSeen updateStatement = new UpdateLastSeen(getWritableDb(), AutocryptPeer.FACTORY); updateStatement.bind(packageName, autocryptId, date); int updated = updateStatement.executeUpdateDelete(); if (updated == 0) { - InsertPeer insertStatement = new InsertPeer(db, AutocryptPeer.FACTORY); + InsertPeer insertStatement = new InsertPeer(getWritableDb(), AutocryptPeer.FACTORY); insertStatement.bind(packageName, autocryptId, date); insertStatement.executeInsert(); } @@ -112,30 +107,30 @@ public class AutocryptPeerDao { public void updateKey(String packageName, String autocryptId, Date effectiveDate, long masterKeyId, boolean isMutual) { - UpdateKey updateStatement = new UpdateKey(db, AutocryptPeer.FACTORY); + UpdateKey updateStatement = new UpdateKey(getWritableDb(), AutocryptPeer.FACTORY); updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, isMutual); int rowsUpdated = updateStatement.executeUpdateDelete(); if (rowsUpdated == 0) { throw new IllegalStateException("No rows updated! Was this peer inserted before the update?"); } - databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId); + getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId); } public void updateKeyGossip(String packageName, String autocryptId, Date effectiveDate, long masterKeyId, GossipOrigin origin) { - UpdateGossipKey updateStatement = new UpdateGossipKey(db, AutocryptPeer.FACTORY); + UpdateGossipKey updateStatement = new UpdateGossipKey(getWritableDb(), AutocryptPeer.FACTORY); updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, origin); int rowsUpdated = updateStatement.executeUpdateDelete(); if (rowsUpdated == 0) { throw new IllegalStateException("No rows updated! Was this peer inserted before the update?"); } - databaseNotifyManager.notifyAutocryptUpdate(autocryptId, masterKeyId); + getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId); } public List getAutocryptPeersForKey(long masterKeyId) { ArrayList result = new ArrayList<>(); SqlDelightQuery query = AutocryptPeer.FACTORY.selectByMasterKeyId(masterKeyId); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToNext()) { AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor); result.add(autocryptPeer); @@ -146,16 +141,16 @@ public class AutocryptPeerDao { public void deleteByIdentifier(String packageName, String autocryptId) { Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId); - DeleteByIdentifier deleteStatement = new DeleteByIdentifier(db); + DeleteByIdentifier deleteStatement = new DeleteByIdentifier(getReadableDb()); deleteStatement.bind(packageName, autocryptId); deleteStatement.execute(); if (masterKeyId != null) { - databaseNotifyManager.notifyAutocryptDelete(autocryptId, masterKeyId); + getDatabaseNotifyManager().notifyAutocryptDelete(autocryptId, masterKeyId); } } public void deleteByMasterKeyId(long masterKeyId) { - DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db); + DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getReadableDb()); deleteStatement.bind(masterKeyId); deleteStatement.execute(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java index f98258c8b..94f458aee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java @@ -6,7 +6,6 @@ import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; import android.database.Cursor; @@ -15,26 +14,21 @@ import org.sufficientlysecure.keychain.KeyMetadataModel.ReplaceKeyMetadata; import org.sufficientlysecure.keychain.model.KeyMetadata; -public class KeyMetadataDao { - private final SupportSQLiteDatabase db; - private DatabaseNotifyManager databaseNotifyManager; - - +public class KeyMetadataDao extends AbstractDao { public static KeyMetadataDao create(Context context) { - SupportSQLiteDatabase supportSQLiteDatabase = new KeychainDatabase(context).getWritableDatabase(); + KeychainDatabase database = new KeychainDatabase(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - return new KeyMetadataDao(supportSQLiteDatabase, databaseNotifyManager); + return new KeyMetadataDao(database, databaseNotifyManager); } - private KeyMetadataDao(SupportSQLiteDatabase supportSQLiteDatabase, DatabaseNotifyManager databaseNotifyManager) { - this.db = supportSQLiteDatabase; - this.databaseNotifyManager = databaseNotifyManager; + private KeyMetadataDao(KeychainDatabase database, DatabaseNotifyManager databaseNotifyManager) { + super(database, databaseNotifyManager); } public KeyMetadata getKeyMetadata(long masterKeyId) { SqlDelightQuery query = KeyMetadata.FACTORY.selectByMasterKeyId(masterKeyId); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToFirst()) { return KeyMetadata.FACTORY.selectByMasterKeyIdMapper().map(cursor); } @@ -43,22 +37,22 @@ public class KeyMetadataDao { } public void resetAllLastUpdatedTimes() { - new KeyMetadata.DeleteAllLastUpdatedTimes(db).execute(); + new KeyMetadata.DeleteAllLastUpdatedTimes(getWritableDb()).execute(); } public void renewKeyLastUpdatedTime(long masterKeyId, boolean seenOnKeyservers) { - ReplaceKeyMetadata replaceStatement = new ReplaceKeyMetadata(db, KeyMetadata.FACTORY); + ReplaceKeyMetadata replaceStatement = new ReplaceKeyMetadata(getWritableDb(), KeyMetadata.FACTORY); replaceStatement.bind(masterKeyId, new Date(), seenOnKeyservers); replaceStatement.executeInsert(); - databaseNotifyManager.notifyKeyMetadataChange(masterKeyId); + getDatabaseNotifyManager().notifyKeyMetadataChange(masterKeyId); } public List getFingerprintsForKeysOlderThan(long olderThan, TimeUnit timeUnit) { SqlDelightQuery query = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThan(new Date(timeUnit.toMillis(olderThan))); List fingerprintList = new ArrayList<>(); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { while (cursor.moveToNext()) { byte[] fingerprint = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThanMapper().map(cursor); fingerprintList.add(fingerprint); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 5db3f9cd0..c8ca026f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; @@ -44,7 +43,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import timber.log.Timber; -public class KeyRepository { +public class KeyRepository extends AbstractDao { // If we ever switch to api level 11, we can ditch this whole mess! public static final int FIELD_TYPE_NULL = 1; // this is called integer to stay coherent with the constants in Cursor (api level 11) @@ -56,7 +55,6 @@ public class KeyRepository { final ContentResolver contentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; final LocalSecretKeyStorage localSecretKeyStorage; - final SupportSQLiteDatabase db; OperationLog mLog; int mIndent; @@ -65,23 +63,26 @@ public class KeyRepository { ContentResolver contentResolver = context.getContentResolver(); LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); - SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase(); + KeychainDatabase database = new KeychainDatabase(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - return new KeyRepository(contentResolver, db, localPublicKeyStorage, localSecretKeyStorage); + return new KeyRepository(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage); } - private KeyRepository(ContentResolver contentResolver, SupportSQLiteDatabase db, + private KeyRepository(ContentResolver contentResolver, KeychainDatabase database, + DatabaseNotifyManager databaseNotifyManager, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage) { - this(contentResolver, db, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0); + this(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0); } - KeyRepository(ContentResolver contentResolver, SupportSQLiteDatabase db, + KeyRepository(ContentResolver contentResolver, KeychainDatabase database, + DatabaseNotifyManager databaseNotifyManager, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, OperationLog log, int indent) { + super(database, databaseNotifyManager); this.contentResolver = contentResolver; - this.db = db; mLocalPublicKeyStorage = localPublicKeyStorage; this.localSecretKeyStorage = localSecretKeyStorage; mIndent = indent; @@ -288,7 +289,7 @@ public class KeyRepository { public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException { SqlDelightQuery query = KeyRingPublic.FACTORY.selectByMasterKeyId(masterKeyId); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToFirst()) { KeyRingPublic keyRingPublic = KeyRingPublic.MAPPER.map(cursor); byte[] keyRingData = keyRingPublic.key_ring_data(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index a32fdacb0..7cabe3de5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Context; @@ -92,25 +91,26 @@ public class KeyWritableRepository extends KeyRepository { LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); - SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase(); - return new KeyWritableRepository(context, db, + KeychainDatabase database = new KeychainDatabase(context); + + return new KeyWritableRepository(context, database, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, autocryptPeerDao); } @VisibleForTesting KeyWritableRepository(Context context, - SupportSQLiteDatabase db, LocalPublicKeyStorage localPublicKeyStorage, + KeychainDatabase database, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, AutocryptPeerDao autocryptPeerDao) { - this(context, db, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0, + this(context, database, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, new OperationLog(), 0, autocryptPeerDao); } - private KeyWritableRepository(Context context, SupportSQLiteDatabase db, + private KeyWritableRepository(Context context, KeychainDatabase database, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, OperationLog log, int indent, AutocryptPeerDao autocryptPeerDao) { - super(context.getContentResolver(), db, localPublicKeyStorage, localSecretKeyStorage, log, indent); + super(context.getContentResolver(), database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage, log, indent); this.context = context; this.databaseNotifyManager = databaseNotifyManager; @@ -535,9 +535,11 @@ public class KeyWritableRepository extends KeyRepository { try { // delete old version of this keyRing (from database only!), which also deletes all keys and userIds on cascade - int deleted = contentResolver.delete( - KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); - if (deleted > 0) { + DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getWritableDb()); + deleteStatement.bind(masterKeyId); + int deletedRows = deleteStatement.executeUpdateDelete(); + + if (deletedRows > 0) { log(LogType.MSG_IP_DELETE_OLD_OK); result |= SaveKeyringResult.UPDATED; } else { @@ -595,7 +597,7 @@ public class KeyWritableRepository extends KeyRepository { } autocryptPeerDao.deleteByMasterKeyId(masterKeyId); - DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(db); + DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getWritableDb()); deleteStatement.bind(masterKeyId); int deletedRows = deleteStatement.executeUpdateDelete(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java index 483dd7419..5d18431de 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java @@ -26,7 +26,7 @@ import android.net.Uri; * from {#android.content.ContentResolver}. It is used to allow substitution * of a ContentResolver in DAOs. * - * @see ApiDataAccessObject + * @see ApiAppDao */ public interface SimpleContentResolverInterface { Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java index fa6d04bbd..365bd92d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java @@ -35,7 +35,7 @@ import android.os.Binder; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import timber.log.Timber; @@ -45,13 +45,13 @@ import timber.log.Timber; public class ApiPermissionHelper { private final Context mContext; - private final ApiDataAccessObject mApiDao; + private final ApiAppDao mApiAppDao; private PackageManager mPackageManager; - public ApiPermissionHelper(Context context, ApiDataAccessObject apiDao) { + public ApiPermissionHelper(Context context, ApiAppDao apiAppDao) { mContext = context; mPackageManager = context.getPackageManager(); - mApiDao = apiDao; + mApiAppDao = apiAppDao; } public static class WrongPackageCertificateException extends Exception { @@ -206,7 +206,7 @@ public class ApiPermissionHelper { public boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException { Timber.d("isPackageAllowed packageName: " + packageName); - byte[] storedPackageCert = mApiDao.getApiAppCertificate(packageName); + byte[] storedPackageCert = mApiAppDao.getApiAppCertificate(packageName); boolean isKnownPackage = storedPackageCert != null; if (!isKnownPackage) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 2d146fe77..29550b3a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -38,7 +38,7 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; @@ -102,7 +102,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC throw new NullPointerException("Context can't be null during onCreate!"); } - apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext())); + apiPermissionHelper = new ApiPermissionHelper(context, ApiAppDao.getInstance(getContext())); return true; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 2a7fe0874..3591df50f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -67,7 +67,7 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; @@ -97,7 +97,7 @@ public class OpenPgpService extends Service { private ApiPermissionHelper mApiPermissionHelper; private KeyRepository mKeyRepository; - private ApiDataAccessObject mApiDao; + private ApiAppDao mApiAppDao; private OpenPgpServiceKeyIdExtractor mKeyIdExtractor; private ApiPendingIntentFactory mApiPendingIntentFactory; @@ -105,8 +105,8 @@ public class OpenPgpService extends Service { public void onCreate() { super.onCreate(); mKeyRepository = KeyRepository.create(this); - mApiDao = new ApiDataAccessObject(this); - mApiPermissionHelper = new ApiPermissionHelper(this, mApiDao); + mApiAppDao = ApiAppDao.getInstance(this); + mApiPermissionHelper = new ApiPermissionHelper(this, mApiAppDao); mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext()); mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory); } @@ -915,7 +915,7 @@ public class OpenPgpService extends Service { private HashSet getAllowedKeyIds() { String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - return mApiDao.getAllowedKeyIdsForApp(currentPkg); + return mApiAppDao.getAllowedKeyIdsForApp(currentPkg); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java index 85c6b2dad..faa2f805e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java @@ -23,7 +23,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; public class PackageUninstallReceiver extends BroadcastReceiver { @@ -36,8 +36,8 @@ public class PackageUninstallReceiver extends BroadcastReceiver { } String packageName = uri.getEncodedSchemeSpecificPart(); - ApiDataAccessObject apiDao = new ApiDataAccessObject(context); - apiDao.deleteApiApp(packageName); + ApiAppDao apiAppDao = ApiAppDao.getInstance(context); + apiAppDao.deleteApiApp(packageName); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java index aead4985c..4851d0815 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java @@ -45,7 +45,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -63,7 +63,7 @@ public class SshAuthenticationService extends Service { private ApiPermissionHelper mApiPermissionHelper; private KeyRepository mKeyRepository; - private ApiDataAccessObject mApiDao; + private ApiAppDao mApiAppDao; private ApiPendingIntentFactory mApiPendingIntentFactory; private static final List SUPPORTED_VERSIONS = Collections.unmodifiableList(Collections.singletonList(1)); @@ -74,9 +74,9 @@ public class SshAuthenticationService extends Service { @Override public void onCreate() { super.onCreate(); - mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this)); + mApiPermissionHelper = new ApiPermissionHelper(this, ApiAppDao.getInstance(this)); mKeyRepository = KeyRepository.create(this); - mApiDao = new ApiDataAccessObject(this); + mApiAppDao = ApiAppDao.getInstance(this); mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext()); } @@ -394,7 +394,7 @@ public class SshAuthenticationService extends Service { private HashSet getAllowedKeyIds() { String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - return mApiDao.getAllowedKeyIdsForApp(currentPkg); + return mApiAppDao.getAllowedKeyIdsForApp(currentPkg); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index 79c54147e..cd8448f83 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -37,7 +37,7 @@ import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import timber.log.Timber; @@ -54,7 +54,7 @@ public class AppSettingsActivity extends BaseActivity { // model ApiApp mApiApp; - private ApiDataAccessObject apiDataAccessObject; + private ApiAppDao apiAppDao; @Override protected void onCreate(Bundle savedInstanceState) { @@ -76,7 +76,7 @@ public class AppSettingsActivity extends BaseActivity { return; } - apiDataAccessObject = new ApiDataAccessObject(this); + apiAppDao = ApiAppDao.getInstance(this); loadData(savedInstanceState); } @@ -170,7 +170,7 @@ public class AppSettingsActivity extends BaseActivity { } private void loadData(Bundle savedInstanceState) { - mApiApp = apiDataAccessObject.getApiApp(packageName); + mApiApp = apiAppDao.getApiApp(packageName); // get application name and icon from package manager String appName; @@ -211,7 +211,7 @@ public class AppSettingsActivity extends BaseActivity { } private void revokeAccess() { - apiDataAccessObject.deleteApiApp(packageName); + apiAppDao.deleteApiApp(packageName); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index 5c8b99926..ee9dbbf6c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -33,7 +33,7 @@ import android.widget.ListView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter; @@ -44,7 +44,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i private static final String ARG_PACKAGE_NAME = "package_name"; private KeySelectableAdapter mAdapter; - private ApiDataAccessObject mApiDao; + private ApiAppDao mApiAppDao; private String packageName; @@ -66,7 +66,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mApiDao = new ApiDataAccessObject(getActivity()); + mApiAppDao = ApiAppDao.getInstance(getActivity()); } @Override @@ -104,7 +104,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - Set checked = mApiDao.getAllowedKeyIdsForApp(packageName); + Set checked = mApiAppDao.getAllowedKeyIdsForApp(packageName); mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked); setListAdapter(mAdapter); getListView().setOnItemClickListener(mAdapter); @@ -137,7 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i } */ public void saveAllowedKeys() { - mApiDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds()); + mApiAppDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index e55b1e19f..7f0a24c5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -41,7 +41,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.ApiApp; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; @@ -169,14 +169,14 @@ public class AppsListFragment extends RecyclerFragment { } public static class ApiAppsLiveData extends AsyncTaskLiveData> { - private final ApiDataAccessObject apiDao; + private final ApiAppDao apiAppDao; private final PackageManager packageManager; ApiAppsLiveData(Context context) { super(context, null); packageManager = getContext().getPackageManager(); - apiDao = new ApiDataAccessObject(context); + apiAppDao = ApiAppDao.getInstance(context); } @Override @@ -191,7 +191,7 @@ public class AppsListFragment extends RecyclerFragment { } private void loadRegisteredApps(ArrayList result) { - List registeredApiApps = apiDao.getAllApiApps(); + List registeredApiApps = apiAppDao.getAllApiApps(); for (ApiApp apiApp : registeredApiApps) { ListedApp listedApp; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java index 66b4f2965..22d7dba0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java @@ -27,12 +27,12 @@ import android.graphics.drawable.Drawable; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.ApiApp; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import timber.log.Timber; class RemoteRegisterPresenter { - private final ApiDataAccessObject apiDao; + private final ApiAppDao apiAppDao; private final PackageManager packageManager; private final Context context; @@ -45,7 +45,7 @@ class RemoteRegisterPresenter { RemoteRegisterPresenter(Context context) { this.context = context; - apiDao = new ApiDataAccessObject(context); + apiAppDao = ApiAppDao.getInstance(context); packageManager = context.getPackageManager(); } @@ -76,7 +76,7 @@ class RemoteRegisterPresenter { } void onClickAllow() { - apiDao.insertApiApp(apiApp); + apiAppDao.insertApiApp(apiApp); view.finishWithResult(resultData); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java index a3c4e1bad..1a8e6e2c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java @@ -29,7 +29,7 @@ import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; @@ -42,7 +42,7 @@ import timber.log.Timber; class RequestKeyPermissionPresenter { private final Context context; private final PackageManager packageManager; - private final ApiDataAccessObject apiDataAccessObject; + private final ApiAppDao apiAppDao; private final ApiPermissionHelper apiPermissionHelper; private RequestKeyPermissionMvpView view; @@ -54,19 +54,19 @@ class RequestKeyPermissionPresenter { static RequestKeyPermissionPresenter createRequestKeyPermissionPresenter(Context context) { PackageManager packageManager = context.getPackageManager(); - ApiDataAccessObject apiDataAccessObject = new ApiDataAccessObject(context); - ApiPermissionHelper apiPermissionHelper = new ApiPermissionHelper(context, apiDataAccessObject); + ApiAppDao apiAppDao = ApiAppDao.getInstance(context); + ApiPermissionHelper apiPermissionHelper = new ApiPermissionHelper(context, apiAppDao); KeyRepository keyRepository = KeyRepository.create(context); - return new RequestKeyPermissionPresenter(context, apiDataAccessObject, apiPermissionHelper, packageManager, + return new RequestKeyPermissionPresenter(context, apiAppDao, apiPermissionHelper, packageManager, keyRepository); } - private RequestKeyPermissionPresenter(Context context, ApiDataAccessObject apiDataAccessObject, + private RequestKeyPermissionPresenter(Context context, ApiAppDao apiAppDao, ApiPermissionHelper apiPermissionHelper, PackageManager packageManager, KeyRepository keyRepository) { this.context = context; - this.apiDataAccessObject = apiDataAccessObject; + this.apiAppDao = apiAppDao; this.apiPermissionHelper = apiPermissionHelper; this.packageManager = packageManager; this.keyRepository = keyRepository; @@ -160,7 +160,7 @@ class RequestKeyPermissionPresenter { } void onClickAllow() { - apiDataAccessObject.addAllowedKeyIdForApp(packageName, masterKeyId); + apiAppDao.addAllowedKeyIdForApp(packageName, masterKeyId); view.finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index ff5940a05..3f8c4b905 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -32,7 +32,7 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -49,7 +49,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment Date: Fri, 22 Jun 2018 11:42:39 +0200 Subject: [PATCH 025/124] more Dao work --- .../pgp/OpenPgpSignatureResultBuilder.java | 6 +-- .../keychain/provider/AbstractDao.java | 18 ++++++++ .../keychain/provider/KeyRepository.java | 32 ++++++-------- .../keychain/ui/EditKeyFragment.java | 1 - .../keychain/util/ContactHelper.java | 43 ++++++++----------- .../keychain/UserPackets.sq | 6 +++ 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index f7034800b..fa6f97f8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -127,11 +127,7 @@ public class OpenPgpSignatureResultBuilder { ArrayList allUserIds = signingRing.getUnorderedUserIds(); ArrayList confirmedUserIds; - try { - confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); - } catch (NotFoundException e) { - throw new IllegalStateException("Key didn't exist anymore for user id query!", e); - } + confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); setUserIds(allUserIds, confirmedUserIds); mSenderStatusResult = processSenderStatusResult(allUserIds, confirmedUserIds); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java index 89e084989..9ad28f8d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java @@ -1,7 +1,12 @@ package org.sufficientlysecure.keychain.provider; +import java.util.ArrayList; +import java.util.List; + import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteQuery; +import android.database.Cursor; class AbstractDao { @@ -24,4 +29,17 @@ class AbstractDao { DatabaseNotifyManager getDatabaseNotifyManager() { return databaseNotifyManager; } + + List mapAllRows(SupportSQLiteQuery query, Mapper mapper) { + ArrayList result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + T item = mapper.map(cursor); + result.add(item); + } + return result; + } + + interface Mapper { + T map(Cursor cursor); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index c8ca026f4..6c278f2c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import android.content.ContentResolver; import android.content.Context; @@ -31,6 +32,8 @@ import android.net.Uri; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.model.KeyRingPublic; +import org.sufficientlysecure.keychain.model.UserPacket; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; @@ -39,7 +42,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import timber.log.Timber; @@ -239,25 +241,19 @@ public class KeyRepository extends AbstractDao { } } - public ArrayList getConfirmedUserIds(long masterKeyId) throws NotFoundException { - Cursor cursor = contentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), - new String[]{UserPackets.USER_ID}, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null - ); - if (cursor == null) { - throw new NotFoundException("Key id for requested user ids not found"); - } + public List getUserIds(long masterKeyId) { + SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyId); + return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); + } - try { - ArrayList userIds = new ArrayList<>(cursor.getCount()); - while (cursor.moveToNext()) { - String userId = cursor.getString(0); - userIds.add(userId); - } - - return userIds; - } finally { - cursor.close(); + public ArrayList getConfirmedUserIds(long masterKeyId) { + ArrayList userIds = new ArrayList<>(); + SqlDelightQuery query = + UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(masterKeyId, Certs.VERIFIED_SECRET); + for (UserId userId : mapAllRows(query, UserPacket.USER_ID_MAPPER::map)) { + userIds.add(userId.user_id()); } + return userIds; } private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException { 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 aec5fac3c..10c62dda3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -67,7 +67,6 @@ import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.util.Passphrase; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 40bc57519..985880b75 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -42,20 +42,22 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.Data; import android.support.v4.content.ContextCompat; import android.util.Patterns; -import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import timber.log.Timber; public class ContactHelper { private static final Map photoCache = new HashMap<>(); + private final KeyRepository keyRepository; private Context mContext; private ContentResolver mContentResolver; @@ -63,6 +65,7 @@ public class ContactHelper { public ContactHelper(Context context) { mContext = context; mContentResolver = context.getContentResolver(); + keyRepository = KeyRepository.create(context); } public List getPossibleUserEmails() { @@ -838,29 +841,17 @@ public class ContactHelper { */ private void writeContactEmail(ArrayList ops, long rawContactId, long masterKeyId) { - ops.add(selectByRawContactAndItemType( - ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI), - rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build()); - Cursor ids = mContentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), - new String[]{ - UserPackets.USER_ID - }, - UserPackets.IS_REVOKED + "=0", - null, null); - if (ids != null) { - while (ids.moveToNext()) { - OpenPgpUtils.UserId userId = KeyRing.splitUserId(ids.getString(0)); - if (userId.email != null) { - ops.add(referenceRawContact( - ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), - rawContactId) - .withValue(ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) - .withValue(ContactsContract.CommonDataKinds.Email.DATA, userId.email) - .build()); - } - } - ids.close(); + ContentProviderOperation deleteOp = selectByRawContactAndItemType( + ContentProviderOperation.newDelete(Data.CONTENT_URI), rawContactId, Email.CONTENT_ITEM_TYPE).build(); + ops.add(deleteOp); + + for (UserId userId : keyRepository.getUserIds(masterKeyId)) { + ContentProviderOperation insertOp = + referenceRawContact(ContentProviderOperation.newInsert(Data.CONTENT_URI), rawContactId) + .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) + .withValue(Email.DATA, userId.email()) + .build(); + ops.add(insertOp); } } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq index 062869b34..42bca407d 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq @@ -22,6 +22,12 @@ SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comm LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ?; +selectUserIdsByMasterKeyIdAndVerification: +SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, certs.verified + FROM user_packets + LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) + WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ? AND certs.verified = ?; + selectUserAttributesByTypeAndMasterKeyId: SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary, is_revoked, certs.verified FROM user_packets From cf0b659e78cc58315ff9abc5386fa406e9066a5d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 22 Jun 2018 13:24:41 +0200 Subject: [PATCH 026/124] Some cleanup in user id loading --- .../keychain/provider/AbstractDao.java | 6 +- .../keychain/provider/KeychainContract.java | 16 - .../keychain/provider/KeychainProvider.java | 13 +- .../keychain/ui/EditKeyActivity.java | 20 +- .../keychain/ui/EditKeyFragment.java | 403 +----------------- .../ui/ViewKeyAdvUserIdsFragment.java | 58 +-- .../ui/adapter/UserAttributesAdapter.java | 75 ---- .../keychain/ui/adapter/UserIdsAdapter.java | 104 +++-- .../src/main/res/layout/edit_key_fragment.xml | 20 - 9 files changed, 130 insertions(+), 585 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java index 9ad28f8d8..0fbb25ba0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java @@ -33,8 +33,10 @@ class AbstractDao { List mapAllRows(SupportSQLiteQuery query, Mapper mapper) { ArrayList result = new ArrayList<>(); try (Cursor cursor = getReadableDb().query(query)) { - T item = mapper.map(cursor); - result.add(item); + while (cursor.moveToNext()) { + T item = mapper.map(cursor); + result.add(item); + } } return result; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 696d6e1d4..4d3db4a13 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -229,18 +229,6 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.user_ids"; - - /** - * Use if a single item is returned - */ - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.user_ids"; - public static Uri buildUserIdsUri() { return CONTENT_URI.buildUpon().appendPath(PATH_USER_IDS).build(); } @@ -248,10 +236,6 @@ public class KeychainContract { public static Uri buildUserIdsUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build(); } - - public static Uri buildUserIdsUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build(); - } } public static class Certs implements CertsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 40398f741..d5481f5ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -177,9 +177,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case KEY_RING_KEYS: return Keys.CONTENT_TYPE; - case KEY_RING_USER_IDS: - return UserPackets.CONTENT_TYPE; - case KEY_SIGNATURES: return KeySignatures.CONTENT_TYPE; @@ -466,8 +463,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case KEY_RINGS_USER_IDS: - case KEY_RING_USER_IDS: { + case KEY_RINGS_USER_IDS: { HashMap projectionMap = new HashMap<>(); projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id"); projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID); @@ -497,13 +493,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); - // If we are searching for a particular keyring's ids, add where - if (match == KEY_RING_USER_IDS) { - qb.appendWhere(" AND "); - qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - } - if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" + "," + Tables.USER_PACKETS + "." + UserPackets.RANK + " ASC"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 7e5995a80..90184bc00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -17,7 +17,7 @@ package org.sufficientlysecure.keychain.ui; -import android.net.Uri; + import android.os.Bundle; import org.sufficientlysecure.keychain.R; @@ -27,24 +27,20 @@ import timber.log.Timber; public class EditKeyActivity extends BaseActivity { - public static final String EXTRA_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; - private EditKeyFragment mEditKeyFragment; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Uri dataUri = getIntent().getData(); SaveKeyringParcel saveKeyringParcel = getIntent().getParcelableExtra(EXTRA_SAVE_KEYRING_PARCEL); - if (dataUri == null && saveKeyringParcel == null) { + if (saveKeyringParcel == null) { Timber.e("Either a key Uri or EXTRA_SAVE_KEYRING_PARCEL is required!"); finish(); return; } - loadFragment(savedInstanceState, dataUri, saveKeyringParcel); + loadFragment(savedInstanceState, saveKeyringParcel); } @Override @@ -52,7 +48,7 @@ public class EditKeyActivity extends BaseActivity { setContentView(R.layout.edit_key_activity); } - private void loadFragment(Bundle savedInstanceState, Uri dataUri, SaveKeyringParcel saveKeyringParcel) { + private void loadFragment(Bundle savedInstanceState, SaveKeyringParcel saveKeyringParcel) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -61,16 +57,12 @@ public class EditKeyActivity extends BaseActivity { } // Create an instance of the fragment - if (dataUri != null) { - mEditKeyFragment = EditKeyFragment.newInstance(dataUri); - } else { - mEditKeyFragment = EditKeyFragment.newInstance(saveKeyringParcel); - } + EditKeyFragment editKeyFragment = EditKeyFragment.newInstance(saveKeyringParcel); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() - .replace(R.id.edit_key_fragment_container, mEditKeyFragment) + .replace(R.id.edit_key_fragment_container, editKeyFragment) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); 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 10c62dda3..0bbabeab6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -18,103 +18,49 @@ package org.sufficientlysecure.keychain.ui; -import java.util.Date; - import android.app.Activity; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.ListView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; -import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; -import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import timber.log.Timber; -public class EditKeyFragment extends QueueingCryptoOperationFragment - implements LoaderManager.LoaderCallbacks { +public class EditKeyFragment extends Fragment { + private static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; - public static final String ARG_DATA_URI = "uri"; - public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; - - private ListView mUserIdsList; - private ListView mSubkeysList; private ListView mUserIdsAddedList; private ListView mSubkeysAddedList; private View mChangePassphrase; private View mAddUserId; private View mAddSubkey; - private static final int LOADER_ID_USER_IDS = 0; - private static final int LOADER_ID_SUBKEYS = 1; - - // cursor adapter - private UserIdsAdapter mUserIdsAdapter; - private SubkeysAdapter mSubkeysAdapter; - // array adapter private UserIdsAddedAdapter mUserIdsAddedAdapter; private SubkeysAddedAdapter mSubkeysAddedAdapter; - private Uri mDataUri; - private SaveKeyringParcel.Builder mSkpBuilder; private String mPrimaryUserId; - /** - * Creates new instance of this fragment - */ - public static EditKeyFragment newInstance(Uri dataUri) { - EditKeyFragment frag = new EditKeyFragment(); - - Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); - - frag.setArguments(args); - - return frag; - } - public static EditKeyFragment newInstance(SaveKeyringParcel saveKeyringParcel) { EditKeyFragment frag = new EditKeyFragment(); @@ -127,11 +73,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment { + // if we are working on an Uri, save directly + returnKeyringParcel(); + }, + v -> { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); }); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); SaveKeyringParcel saveKeyringParcel = getArguments().getParcelable(ARG_SAVE_KEYRING_PARCEL); - if (dataUri == null && saveKeyringParcel == null) { + if (saveKeyringParcel == null) { Timber.e("Either a key Uri or ARG_SAVE_KEYRING_PARCEL is required!"); getActivity().finish(); return; } initView(); - if (dataUri != null) { - loadData(dataUri); - } else { - loadSaveKeyringParcel(saveKeyringParcel); - } + loadSaveKeyringParcel(saveKeyringParcel); } private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) { @@ -191,146 +121,13 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment parent, View view, int position, long id) { - editSubkey(position); - } - }); - - mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - editUserId(position); - } - }); - } - - public Loader onCreateLoader(int id, Bundle args) { - - switch (id) { - case LOADER_ID_USER_IDS: { - Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, - UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); - } - - case LOADER_ID_SUBKEYS: { - Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, - SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); - } - - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_USER_IDS: - mUserIdsAdapter.swapCursor(data); - break; - - case LOADER_ID_SUBKEYS: - mSubkeysAdapter.swapCursor(data); - break; - - } - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - switch (loader.getId()) { - case LOADER_ID_USER_IDS: - mUserIdsAdapter.swapCursor(null); - break; - case LOADER_ID_SUBKEYS: - mSubkeysAdapter.swapCursor(null); - break; - } + mChangePassphrase.setOnClickListener(v -> changePassphrase()); + mAddUserId.setOnClickListener(v -> addUserId()); + mAddSubkey.setOnClickListener(v -> addSubkey()); } private void changePassphrase() { -// Intent passIntent = new Intent(getActivity(), PassphraseWizardActivity.class); -// passIntent.setAction(PassphraseWizardActivity.CREATE_METHOD); -// startActivityForResult(passIntent, 12); - // Message is received after passphrase is cached Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { @@ -354,138 +151,6 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment finish, return result to showkey and display there! - Intent intent = new Intent(); - intent.putExtra(OperationResult.EXTRA_RESULT, result); - activity.setResult(Activity.RESULT_OK, intent); - activity.finish(); - - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index 6e51ee7a0..9ce7058c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.ui; +import java.util.List; + +import android.arch.lifecycle.LiveData; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -41,10 +44,14 @@ import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; @@ -63,7 +70,6 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "uri"; private static final int LOADER_ID_UNIFIED = 0; - private static final int LOADER_ID_USER_IDS = 1; private ListView mUserIds; private ListView mUserIdsAddedList; @@ -162,7 +168,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements } break; } - getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); + mUserIdsAdapter.notifyDataSetChanged(); } }; @@ -244,13 +250,30 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements Timber.i("dataUri: " + mDataUri); - mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); + mUserIdsAdapter = new UserIdsAdapter(getActivity(), false); mUserIds.setAdapter(mUserIdsAdapter); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + + KeyRepository keyRepository = KeyRepository.create(getContext()); + try { + Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + CachedPublicKeyRing keyRing = keyRepository.getCachedPublicKeyRing(uri); + long masterKeyId = keyRing.getMasterKeyId(); + + LiveData> userIdLiveData = + new GenericLiveData<>(getContext(), null, () -> keyRepository.getUserIds(masterKeyId)); + userIdLiveData.observe(this, this::onUserIdsLoaded); + } catch (PgpKeyNotFoundException e) { + e.printStackTrace(); + } + } + + private void onUserIdsLoaded(List userIds) { + mUserIdsAdapter.setData(userIds); + setContentShown(true); } // These are the rows that we will retrieve. @@ -273,14 +296,6 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements PROJECTION, null, null, null); } - case LOADER_ID_USER_IDS: { - setContentShown(false); - - Uri userIdUri = UserPackets.buildUserIdsUri(mDataUri); - return new CursorLoader(getActivity(), userIdUri, - UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); - } - default: return null; } @@ -301,14 +316,6 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements mFingerprint = data.getBlob(INDEX_FINGERPRINT); break; } - case LOADER_ID_USER_IDS: { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mUserIdsAdapter.swapCursor(data); - - setContentShown(true); - break; - } } } @@ -317,10 +324,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { - if (loader.getId() != LOADER_ID_USER_IDS) { - return; - } - mUserIdsAdapter.swapCursor(null); + } @Override @@ -352,7 +356,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements mUserIdAddFabLayout.setDisplayedChild(1); mUserIdsAdapter.setEditMode(mSkpBuilder); - getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, ViewKeyAdvUserIdsFragment.this); + mUserIdsAdapter.notifyDataSetChanged(); mode.setTitle(R.string.title_edit_identities); mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); @@ -377,7 +381,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements mUserIdsAdapter.setEditMode(null); mUserIdsAddedLayout.setVisibility(View.GONE); mUserIdAddFabLayout.setDisplayedChild(0); - getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, ViewKeyAdvUserIdsFragment.this); + mUserIdsAdapter.notifyDataSetChanged(); } }); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java deleted file mode 100644 index ac9fbd636..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.widget.CursorAdapter; -import android.view.View; - -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; - -public abstract class UserAttributesAdapter extends CursorAdapter { - public static final String[] USER_PACKETS_PROJECTION = new String[]{ - UserPackets._ID, - UserPackets.TYPE, - UserPackets.USER_ID, - UserPackets.ATTRIBUTE_DATA, - UserPackets.RANK, - UserPackets.VERIFIED, - UserPackets.IS_PRIMARY, - UserPackets.IS_REVOKED, - UserPackets.NAME, - UserPackets.EMAIL, - UserPackets.COMMENT, - }; - public static final int INDEX_ID = 0; - public static final int INDEX_TYPE = 1; - public static final int INDEX_USER_ID = 2; - public static final int INDEX_ATTRIBUTE_DATA = 3; - public static final int INDEX_RANK = 4; - public static final int INDEX_VERIFIED = 5; - public static final int INDEX_IS_PRIMARY = 6; - public static final int INDEX_IS_REVOKED = 7; - public static final int INDEX_NAME = 8; - public static final int INDEX_EMAIL = 9; - public static final int INDEX_COMMENT = 10; - - public UserAttributesAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - } - - @Override - public abstract void bindView(View view, Context context, Cursor cursor); - - public String getUserId(int position) { - mCursor.moveToPosition(position); - return mCursor.getString(INDEX_USER_ID); - } - - public boolean getIsRevoked(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(INDEX_IS_REVOKED) > 0; - } - - public int getIsVerified(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(INDEX_VERIFIED); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 8550a5eca..892226b88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -18,41 +18,73 @@ package org.sufficientlysecure.keychain.ui.adapter; +import java.util.List; + import android.content.Context; -import android.database.Cursor; import android.graphics.Typeface; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; -public class UserIdsAdapter extends UserAttributesAdapter { - protected LayoutInflater mInflater; +// TODO move to RecyclerView +public class UserIdsAdapter extends BaseAdapter { + private Context context; + private List data; private SaveKeyringParcel.Builder mSkpBuilder; private boolean mShowStatusImages; + private LayoutInflater layoutInflater; - public UserIdsAdapter(Context context, Cursor c, int flags, boolean showStatusImages) { - super(context, c, flags); - mInflater = LayoutInflater.from(context); + public UserIdsAdapter(Context context, boolean showStatusImages) { + super(); + this.context = context; + this.layoutInflater = LayoutInflater.from(context); mShowStatusImages = showStatusImages; } - public UserIdsAdapter(Context context, Cursor c, int flags) { - this(context, c, flags, true); + @Override + public int getCount() { + return data != null ? data.size() : 0; } @Override - public void bindView(View view, Context context, Cursor cursor) { + public UserId getItem(int position) { + return data.get(position); + } + + @Override + public long getItemId(int position) { + return data.get(position).master_key_id(); + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + View view; + if (convertView != null) { + view = convertView; + } else { + view = layoutInflater.inflate(R.layout.view_key_adv_user_id_item, parent, false); + } + TextView vName = view.findViewById(R.id.user_id_item_name); TextView vAddress = view.findViewById(R.id.user_id_item_address); TextView vComment = view.findViewById(R.id.user_id_item_comment); @@ -62,37 +94,35 @@ public class UserIdsAdapter extends UserAttributesAdapter { ImageView vDeleteButton = view.findViewById(R.id.user_id_item_delete_button); vDeleteButton.setVisibility(View.GONE); // not used - String userId = cursor.getString(INDEX_USER_ID); - String name = cursor.getString(INDEX_NAME); - String email = cursor.getString(INDEX_EMAIL); - String comment = cursor.getString(INDEX_COMMENT); - if (name != null) { - vName.setText(name); + UserId userId = getItem(position); + + if (userId.name() != null) { + vName.setText(userId.name()); } else { vName.setText(R.string.user_id_no_name); } - if (email != null) { - vAddress.setText(email); + if (userId.email() != null) { + vAddress.setText(userId.email()); vAddress.setVisibility(View.VISIBLE); } else { vAddress.setVisibility(View.GONE); } - if (comment != null) { - vComment.setText(comment); + if (userId.comment() != null) { + vComment.setText(userId.comment()); vComment.setVisibility(View.VISIBLE); } else { vComment.setVisibility(View.GONE); } - boolean isPrimary = cursor.getInt(INDEX_IS_PRIMARY) != 0; - boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; + boolean isPrimary = userId.is_primary(); + boolean isRevoked = userId.is_revoked(); // for edit key if (mSkpBuilder != null) { String changePrimaryUserId = mSkpBuilder.getChangePrimaryUserId(); boolean changeAnyPrimaryUserId = (changePrimaryUserId != null); - boolean changeThisPrimaryUserId = (changeAnyPrimaryUserId && changePrimaryUserId.equals(userId)); - boolean revokeThisUserId = (mSkpBuilder.getMutableRevokeUserIds().contains(userId)); + boolean changeThisPrimaryUserId = (changeAnyPrimaryUserId && changePrimaryUserId.equals(userId.user_id())); + boolean revokeThisUserId = (mSkpBuilder.getMutableRevokeUserIds().contains(userId.user_id())); // only if primary user id will be changed // (this is not triggered if the user id is currently the primary one) @@ -114,7 +144,7 @@ public class UserIdsAdapter extends UserAttributesAdapter { if (isRevoked) { // set revocation icon (can this even be primary?) - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.key_flag_gray); + KeyFormattingUtils.setStatusImage(context, vVerified, null, State.REVOKED, R.color.key_flag_gray); // disable revoked user ids vName.setEnabled(false); @@ -133,24 +163,25 @@ public class UserIdsAdapter extends UserAttributesAdapter { vAddress.setTypeface(null, Typeface.NORMAL); } - int isVerified = cursor.getInt(INDEX_VERIFIED); + int isVerified = getIsVerified(position); switch (isVerified) { case Certs.VERIFIED_SECRET: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + KeyFormattingUtils.setStatusImage(context, vVerified, null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; case Certs.VERIFIED_SELF: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); + KeyFormattingUtils.setStatusImage(context, vVerified, null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; default: - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); + KeyFormattingUtils.setStatusImage(context, vVerified, null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR); break; } } + + return view; } public boolean getIsRevokedPending(int position) { - mCursor.moveToPosition(position); - String userId = mCursor.getString(INDEX_USER_ID); + String userId = getUserId(position); boolean isRevokedPending = false; if (mSkpBuilder != null) { @@ -177,8 +208,15 @@ public class UserIdsAdapter extends UserAttributesAdapter { mSkpBuilder = saveKeyringParcel; } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.view_key_adv_user_id_item, null); + public String getUserId(int position) { + return data.get(position).user_id(); + } + + public boolean getIsRevoked(int position) { + return data.get(position).is_revoked(); + } + + public int getIsVerified(int position) { + return data.get(position).verified(); } } diff --git a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml index 84686b2ad..10bb4566f 100644 --- a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml @@ -40,16 +40,6 @@ android:text="@string/section_user_ids" android:layout_weight="1" /> - - - - - - - - Date: Fri, 22 Jun 2018 17:10:02 +0200 Subject: [PATCH 027/124] Extract remaining user id loading from KeychainProvider --- .../keychain/provider/KeyRepository.java | 4 +- .../keychain/provider/KeychainContract.java | 5 - .../keychain/provider/KeychainDatabase.java | 2 +- .../keychain/provider/KeychainProvider.java | 42 ----- .../keychain/ui/MultiUserIdsFragment.java | 143 ++++++------------ .../ui/keyview/loader/IdentityDao.java | 2 +- .../keychain/UserPackets.sq | 9 +- 7 files changed, 53 insertions(+), 154 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 6c278f2c3..43a92f4e0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -241,8 +241,8 @@ public class KeyRepository extends AbstractDao { } } - public List getUserIds(long masterKeyId) { - SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyId); + public List getUserIds(long... masterKeyIds) { + SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds); return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 4d3db4a13..21edee446 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -229,10 +229,6 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - public static Uri buildUserIdsUri() { - return CONTENT_URI.buildUpon().appendPath(PATH_USER_IDS).build(); - } - public static Uri buildUserIdsUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_USER_IDS).build(); } @@ -243,7 +239,6 @@ public class KeychainContract { public static final String NAME = UserPacketsColumns.NAME; public static final String EMAIL = UserPacketsColumns.EMAIL; public static final String COMMENT = UserPacketsColumns.COMMENT; - public static final String SIGNER_UID = "signer_user_id"; public static final int UNVERIFIED = 0; public static final int VERIFIED_SECRET = 1; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index e8a6c7472..5e0e8e2b6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -152,7 +152,7 @@ public class KeychainDatabase { + ")"; public KeychainDatabase(Context context) { - this.context = context; + this.context = context.getApplicationContext(); supportSQLiteOpenHelper = new FrameworkSQLiteOpenHelperFactory() .create(Configuration.builder(context).name(DATABASE_NAME).callback( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index d5481f5ea..48efb2781 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -55,7 +55,6 @@ import static android.database.DatabaseUtils.dumpCursorToString; public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface { private static final int KEY_RINGS_UNIFIED = 101; - private static final int KEY_RINGS_USER_IDS = 104; private static final int KEY_RING_UNIFIED = 200; private static final int KEY_RING_KEYS = 201; @@ -92,9 +91,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_UNIFIED, KEY_RINGS_UNIFIED); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_USER_IDS, - KEY_RINGS_USER_IDS); /* * find by criteria other than master key id @@ -463,44 +459,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case KEY_RINGS_USER_IDS: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id"); - projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID); - projectionMap.put(UserPackets.TYPE, Tables.USER_PACKETS + "." + UserPackets.TYPE); - projectionMap.put(UserPackets.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); - projectionMap.put(UserPackets.NAME, Tables.USER_PACKETS + "." + UserPackets.NAME); - projectionMap.put(UserPackets.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL); - projectionMap.put(UserPackets.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT); - projectionMap.put(UserPackets.ATTRIBUTE_DATA, Tables.USER_PACKETS + "." + UserPackets.ATTRIBUTE_DATA); - projectionMap.put(UserPackets.RANK, Tables.USER_PACKETS + "." + UserPackets.RANK); - projectionMap.put(UserPackets.IS_PRIMARY, Tables.USER_PACKETS + "." + UserPackets.IS_PRIMARY); - projectionMap.put(UserPackets.IS_REVOKED, Tables.USER_PACKETS + "." + UserPackets.IS_REVOKED); - // we take the minimum (>0) here, where "1" is "verified by known secret key" - projectionMap.put(UserPackets.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserPackets.VERIFIED); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.USER_PACKETS - + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " - + Tables.CERTS + "." + Certs.MASTER_KEY_ID - + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " - + Tables.CERTS + "." + Certs.RANK - + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" - + ")"); - groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - + ", " + Tables.USER_PACKETS + "." + UserPackets.RANK; - - qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.TYPE + " IS NULL"); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC" - + "," + Tables.USER_PACKETS + "." + UserPackets.RANK + " ASC"; - } - - break; - } - default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java index dc4a4de3f..6cabeb2a8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -17,67 +17,47 @@ package org.sufficientlysecure.keychain.ui; -import android.database.Cursor; + +import java.util.ArrayList; +import java.util.List; + +import android.arch.lifecycle.LiveData; import android.database.MatrixCursor; -import android.net.Uri; import android.os.Bundle; import android.os.Parcel; +import android.support.annotation.NonNull; 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.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import timber.log.Timber; -import java.util.ArrayList; -public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks{ +public class MultiUserIdsFragment extends Fragment { 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; + ListView userIds; + private MultiUserIdsAdapter userIdsAdapter; - 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, - KeychainContract.UserPackets.NAME, - KeychainContract.UserPackets.EMAIL, - KeychainContract.UserPackets.COMMENT, - }; - 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 static final int INDEX_NAME = 5; - private static final int INDEX_EMAIL = 6; - private static final int INDEX_COMMENT = 7; + private long[] pubMasterKeyIds; @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.multi_user_ids_fragment, null); - - mUserIds = view.findViewById(R.id.view_key_user_ids); - + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.multi_user_ids_fragment, container, false); + userIds = view.findViewById(R.id.view_key_user_ids); return view; } @@ -85,10 +65,11 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS); - if (mPubMasterKeyIds == null) { + FragmentActivity activity = requireActivity(); + pubMasterKeyIds = activity.getIntent().getLongArrayExtra(EXTRA_KEY_IDS); + if (pubMasterKeyIds == null) { Timber.e("List of key ids to certify missing!"); - getActivity().finish(); + activity.finish(); return; } @@ -97,18 +78,22 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load checkedStates = (ArrayList) savedInstanceState.getSerializable(ARG_CHECK_STATES); } - mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility); - mUserIds.setAdapter(mUserIdsAdapter); - mUserIds.setDividerHeight(0); + userIdsAdapter = new MultiUserIdsAdapter(activity, null, 0, checkedStates, checkboxVisibility); + userIds.setAdapter(userIdsAdapter); + userIds.setDividerHeight(0); + + KeyRepository keyRepository = KeyRepository.create(activity); + LiveData> userIdLiveData = + new GenericLiveData<>(getContext(), null, () -> keyRepository.getUserIds(pubMasterKeyIds)); + userIdLiveData.observe(this, this::onUserIdsLoaded); - getLoaderManager().initLoader(0, null, this); } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - ArrayList states = mUserIdsAdapter.getCheckStates(); + ArrayList states = userIdsAdapter.getCheckStates(); // no proper parceling method available :( outState.putSerializable(ARG_CHECK_STATES, states); } @@ -118,40 +103,10 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load throw new AssertionError("Item selection not allowed"); } - return mUserIdsAdapter.getSelectedCertifyActions(); + return userIdsAdapter.getSelectedCertifyActions(); } - @Override - public Loader 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 loader, Cursor data) { - + private void onUserIdsLoaded(List userIds) { MatrixCursor matrix = new MatrixCursor(new String[]{ "_id", "user_data", "grouped" }) { @@ -160,28 +115,25 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load return super.getBlob(column); } }; - data.moveToFirst(); long lastMasterKeyId = 0; String lastName = ""; ArrayList uids = new ArrayList<>(); boolean header = true; + boolean isFirst = true; // Iterate over all rows - while (!data.isAfterLast()) { - long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - String userId = data.getString(INDEX_USER_ID); - String name = data.getString(INDEX_NAME); - + for (UserId userId : userIds) { // Two cases: - boolean grouped = masterKeyId == lastMasterKeyId; - boolean subGrouped = data.isFirst() || grouped && lastName != null && lastName.equals(name); + boolean grouped = userId.master_key_id() == lastMasterKeyId; + boolean subGrouped = isFirst || grouped && lastName != null && lastName.equals(userId.name()); + isFirst = false; // Remember for next loop - lastName = name; + lastName = userId.name(); - Timber.d(Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); + Timber.d(Long.toString(userId.master_key_id(), 16) + (grouped ? "grouped" : "not grouped")); if (!subGrouped) { // 1. This name should NOT be grouped with the previous, so we flush the buffer @@ -203,17 +155,13 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load } // 2. This name should be grouped with the previous, just add to buffer - uids.add(userId); - lastMasterKeyId = masterKeyId; + uids.add(userId.user_id()); + lastMasterKeyId = userId.master_key_id(); // 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 @@ -230,12 +178,7 @@ public class MultiUserIdsFragment extends Fragment implements LoaderManager.Load } - mUserIdsAdapter.swapCursor(matrix); - } - - @Override - public void onLoaderReset(Loader loader) { - mUserIdsAdapter.swapCursor(null); + userIdsAdapter.swapCursor(matrix); } public void setCheckboxVisibility(boolean checkboxVisibility) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index f3a39cccf..0d7c06596 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -172,7 +172,7 @@ public class IdentityDao { return null; } - private void loadUserIds(ArrayList identities, long masterKeyId) { + private void loadUserIds(ArrayList identities, long... masterKeyId) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyId); try (Cursor cursor = db.query(query)) { while (cursor.moveToNext()) { diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq index 42bca407d..85f8551e0 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/UserPackets.sq @@ -20,19 +20,22 @@ selectUserIdsByMasterKeyId: SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, certs.verified FROM user_packets LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) - WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ?; + WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id IN ? + ORDER BY user_packets.master_key_id ASC,user_packets.rank ASC; selectUserIdsByMasterKeyIdAndVerification: SELECT user_packets.master_key_id, user_packets.rank, user_id, name, email, comment, is_primary, is_revoked, certs.verified FROM user_packets LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) - WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ? AND certs.verified = ?; + WHERE user_packets.type IS NULL AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ? AND certs.verified = ? + ORDER BY user_packets.rank ASC; selectUserAttributesByTypeAndMasterKeyId: SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary, is_revoked, certs.verified FROM user_packets LEFT JOIN certs ON ( user_packets.master_key_id = certs.master_key_id AND user_packets.rank = certs.rank AND certs.verified > 0 ) - WHERE user_packets.type = ? AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ?; + WHERE user_packets.type = ? AND user_packets.is_revoked = 0 AND user_packets.master_key_id = ? + ORDER BY user_packets.rank ASC; selectSpecificUserAttribute: SELECT user_packets.master_key_id, user_packets.rank, attribute_data, is_primary, is_revoked, certs.verified From 6cd065a3bd6a387663237e17253d95fc67031117 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 22 Jun 2018 19:23:00 +0200 Subject: [PATCH 028/124] extract subkey loading from KeychainProvider --- .../keychain/livedata/KeyRingDao.java | 8 +- .../keychain/model/CustomColumnAdapters.java | 14 ++ .../keychain/model/{Key.java => SubKey.java} | 16 +- .../pgp/OpenPgpSignatureResultBuilder.java | 17 +- .../provider/CachedPublicKeyRing.java | 19 +- .../keychain/provider/KeyRepository.java | 24 +- .../keychain/provider/KeychainContract.java | 29 --- .../keychain/provider/KeychainProvider.java | 44 +--- .../keychain/ui/BackupRestoreFragment.java | 40 ++-- .../keychain/ui/CreateKeyFinalFragment.java | 27 +-- .../keychain/ui/KeyListFragment.java | 2 +- .../ui/ViewKeyAdvSubkeysFragment.java | 69 +++--- .../ui/adapter/FlexibleKeyDetailsItem.java | 2 +- .../ui/adapter/FlexibleKeyItemFactory.java | 2 +- .../keychain/ui/adapter/SubkeysAdapter.java | 207 ++++++------------ .../ui/keyview/loader/SubkeyStatusDao.java | 129 ++++------- .../org/sufficientlysecure/keychain/Keys.sq | 20 +- 17 files changed, 248 insertions(+), 421 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/{Key.java => SubKey.java} (67%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java index 68efe8010..98298a094 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java @@ -9,8 +9,8 @@ import android.content.Context; import android.database.Cursor; import com.squareup.sqldelight.SqlDelightQuery; -import org.sufficientlysecure.keychain.model.Key; -import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.provider.KeychainDatabase; @@ -28,11 +28,11 @@ public class KeyRingDao { } public List getUnifiedKeyInfo() { - SqlDelightQuery query = Key.FACTORY.selectAllUnifiedKeyInfo(); + SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); List result = new ArrayList<>(); try (Cursor cursor = db.query(query)) { while (cursor.moveToNext()) { - UnifiedKeyInfo unifiedKeyInfo = Key.UNIFIED_KEY_INFO_MAPPER.map(cursor); + UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); result.add(unifiedKeyInfo); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java index e6b9d0ee7..4561f45fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java @@ -7,6 +7,7 @@ import android.support.annotation.NonNull; import com.squareup.sqldelight.ColumnAdapter; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; @@ -50,4 +51,17 @@ public final class CustomColumnAdapters { } } }; + + public static final ColumnAdapter SECRET_KEY_TYPE_ADAPTER = new ColumnAdapter() { + @NonNull + @Override + public SecretKeyType decode(Long databaseValue) { + return databaseValue == null ? SecretKeyType.UNAVAILABLE : SecretKeyType.fromNum(databaseValue.intValue()); + } + + @Override + public Long encode(@NonNull SecretKeyType value) { + return (long) value.getNum(); + } + }; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java similarity index 67% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 523883bc1..36102c73c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Key.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -6,14 +6,23 @@ import java.util.Collections; import java.util.List; import com.google.auto.value.AutoValue; +import com.squareup.sqldelight.RowMapper; import org.sufficientlysecure.keychain.KeysModel; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; @AutoValue -public abstract class Key implements KeysModel { - public static final Factory FACTORY = new Factory<>(AutoValue_Key::new); +public abstract class SubKey implements KeysModel { + public static final Factory FACTORY = + new Factory<>(AutoValue_SubKey::new, CustomColumnAdapters.SECRET_KEY_TYPE_ADAPTER); public static final SelectAllUnifiedKeyInfoMapper UNIFIED_KEY_INFO_MAPPER = - FACTORY.selectAllUnifiedKeyInfoMapper(AutoValue_Key_UnifiedKeyInfo::new); + FACTORY.selectAllUnifiedKeyInfoMapper(AutoValue_SubKey_UnifiedKeyInfo::new); + public static Mapper SUBKEY_MAPPER = new Mapper<>(FACTORY); + public static RowMapper SKT_MAPPER = FACTORY.selectSecretKeyTypeMapper(); + + public boolean expires() { + return expiry() != null; + } @AutoValue public static abstract class UnifiedKeyInfo implements SelectAllUnifiedKeyInfoModel { @@ -45,6 +54,5 @@ public abstract class Key implements KeysModel { } return autocryptPackageNames; } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index fa6f97f8e..53477da28 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.pgp; import java.util.ArrayList; import java.util.Date; +import java.util.List; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult; @@ -27,7 +28,6 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import timber.log.Timber; @@ -41,8 +41,8 @@ public class OpenPgpSignatureResultBuilder { // OpenPgpSignatureResult private String mPrimaryUserId; - private ArrayList mUserIds = new ArrayList<>(); - private ArrayList mConfirmedUserIds; + private List mUserIds = new ArrayList<>(); + private List mConfirmedUserIds; private long mKeyId; private SenderStatusResult mSenderStatusResult; @@ -101,7 +101,7 @@ public class OpenPgpSignatureResultBuilder { this.mIsKeyExpired = keyExpired; } - public void setUserIds(ArrayList userIds, ArrayList confirmedUserIds) { + public void setUserIds(List userIds, List confirmedUserIds) { this.mUserIds = userIds; this.mConfirmedUserIds = confirmedUserIds; } @@ -125,9 +125,8 @@ public class OpenPgpSignatureResultBuilder { } setSignatureKeyCertified(signingRing.getVerified() > 0); - ArrayList allUserIds = signingRing.getUnorderedUserIds(); - ArrayList confirmedUserIds; - confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); + List allUserIds = signingRing.getUnorderedUserIds(); + List confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); setUserIds(allUserIds, confirmedUserIds); mSenderStatusResult = processSenderStatusResult(allUserIds, confirmedUserIds); @@ -138,7 +137,7 @@ public class OpenPgpSignatureResultBuilder { } private SenderStatusResult processSenderStatusResult( - ArrayList allUserIds, ArrayList confirmedUserIds) { + List allUserIds, List confirmedUserIds) { if (mSenderAddress == null) { return SenderStatusResult.UNKNOWN; } @@ -152,7 +151,7 @@ public class OpenPgpSignatureResultBuilder { } } - private static boolean userIdListContainsAddress(String senderAddress, ArrayList confirmedUserIds) { + private static boolean userIdListContainsAddress(String senderAddress, List confirmedUserIds) { for (String rawUserId : confirmedUserIds) { UserId userId = OpenPgpUtils.splitUserId(rawUserId); if (senderAddress.equalsIgnoreCase(userId.email)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 077c72285..57957c64b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -25,7 +25,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import timber.log.Timber; @@ -227,10 +226,6 @@ public class CachedPublicKeyRing extends KeyRing { } } - public boolean hasSecretAuthentication() throws PgpKeyNotFoundException { - return getSecretAuthenticationId() != 0; - } - public long getAuthenticationId() throws PgpKeyNotFoundException { try { Object data = mKeyRepository.getGenericData(mUri, @@ -242,10 +237,6 @@ public class CachedPublicKeyRing extends KeyRing { } } - public boolean hasAuthentication() throws PgpKeyNotFoundException { - return getAuthenticationId() != 0; - } - @Override public int getVerified() throws PgpKeyNotFoundException { try { @@ -270,11 +261,11 @@ public class CachedPublicKeyRing extends KeyRing { } public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { - Object data = mKeyRepository.getGenericData(Keys.buildKeysUri(mUri), - KeyRings.HAS_SECRET, - KeyRepository.FIELD_TYPE_INTEGER, - KeyRings.KEY_ID + " = " + Long.toString(keyId)); - return SecretKeyType.fromNum(((Long) data).intValue()); + SecretKeyType secretKeyType = mKeyRepository.getSecretKeyType(keyId); + if (secretKeyType == null) { + throw new NotFoundException(); + } + return secretKeyType; } public byte[] getEncoded() throws PgpKeyNotFoundException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 43a92f4e0..ce89cc977 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -32,11 +32,13 @@ import android.net.Uri; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.model.KeyRingPublic; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -119,11 +121,6 @@ public class KeyRepository extends AbstractDao { return result; } - Object getGenericData(Uri uri, String column, int type, String selection) - throws NotFoundException { - return getGenericData(uri, new String[]{column}, new int[]{type}, selection).get(column); - } - private HashMap getGenericData(Uri uri, String[] proj, int[] types) throws NotFoundException { return getGenericData(uri, proj, types, null); @@ -246,7 +243,7 @@ public class KeyRepository extends AbstractDao { return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); } - public ArrayList getConfirmedUserIds(long masterKeyId) { + public List getConfirmedUserIds(long masterKeyId) { ArrayList userIds = new ArrayList<>(); SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(masterKeyId, Certs.VERIFIED_SECRET); @@ -256,6 +253,21 @@ public class KeyRepository extends AbstractDao { return userIds; } + public List getSubKeysByMasterKeyId(long masterKeyId) { + SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId); + return mapAllRows(query, SubKey.SUBKEY_MAPPER::map); + } + + public SecretKeyType getSecretKeyType(long keyId) { + SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId); + try (Cursor cursor = getReadableDb().query(query)) { + if (cursor.moveToFirst()) { + return SubKey.SKT_MAPPER.map(cursor); + } + return null; + } + } + private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = new ArmoredOutputStream(bos); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 21edee446..0a39dc397 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -130,11 +130,6 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_rings"; - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.key_rings"; - public static Uri buildUnifiedKeyRingsUri() { return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); } @@ -194,34 +189,15 @@ public class KeychainContract { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.keychain.keys"; - - /** - * Use if a single item is returned - */ - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.keychain.keys"; - public static Uri buildKeysUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).appendPath(PATH_KEYS).build(); } - public static Uri buildKeysUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_KEYS).build(); - } - } public static class KeySignatures implements KeySignaturesColumns, BaseColumns { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_SIGNATURES).build(); - - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.key_signatures"; } public static class UserPackets implements UserPacketsColumns, BaseColumns { @@ -235,11 +211,6 @@ public class KeychainContract { } public static class Certs implements CertsColumns, BaseColumns { - public static final String USER_ID = UserPacketsColumns.USER_ID; - public static final String NAME = UserPacketsColumns.NAME; - public static final String EMAIL = UserPacketsColumns.EMAIL; - public static final String COMMENT = UserPacketsColumns.COMMENT; - public static final int UNVERIFIED = 0; public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SELF = 2; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 48efb2781..40fae66eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -168,17 +168,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe */ @Override public String getType(@NonNull Uri uri) { - final int match = mUriMatcher.match(uri); - switch (match) { - case KEY_RING_KEYS: - return Keys.CONTENT_TYPE; - - case KEY_SIGNATURES: - return KeySignatures.CONTENT_TYPE; - - default: - throw new UnsupportedOperationException("Unknown uri: " + uri); - } + throw new UnsupportedOperationException(); } /** @@ -194,7 +184,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe int match = mUriMatcher.match(uri); // all query() parameters, for good measure - String groupBy = null, having = null; + String groupBy; switch (match) { case KEY_RING_UNIFIED: @@ -431,34 +421,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe break; } - case KEY_RING_KEYS: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id"); - projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); - projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK); - projectionMap.put(Keys.KEY_ID, Keys.KEY_ID); - projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE); - projectionMap.put(Keys.KEY_CURVE_OID, Keys.KEY_CURVE_OID); - projectionMap.put(Keys.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED); - projectionMap.put(Keys.IS_SECURE, Tables.KEYS + "." + Keys.IS_SECURE); - projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY); - projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); - projectionMap.put(Keys.CAN_SIGN, Keys.CAN_SIGN); - projectionMap.put(Keys.CAN_AUTHENTICATE, Keys.CAN_AUTHENTICATE); - projectionMap.put(Keys.HAS_SECRET, Keys.HAS_SECRET); - projectionMap.put(Keys.CREATION, Keys.CREATION); - projectionMap.put(Keys.EXPIRY, Keys.EXPIRY); - projectionMap.put(Keys.ALGORITHM, Keys.ALGORITHM); - projectionMap.put(Keys.FINGERPRINT, Keys.FINGERPRINT); - qb.setProjectionMap(projectionMap); - - qb.setTables(Tables.KEYS); - qb.appendWhere(Keys.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - - break; - } - default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -489,7 +451,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) { - String rawQuery = qb.buildQuery(projection, selection, groupBy, having, orderBy, null); + String rawQuery = qb.buildQuery(projection, selection, groupBy, null, orderBy, null); DatabaseUtil.explainQuery(db, rawQuery); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java index 682b0715a..84c2882d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import java.util.ArrayList; import java.util.Iterator; @@ -36,10 +37,10 @@ import android.view.View; import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; @@ -138,32 +139,19 @@ public class BackupRestoreFragment extends Fragment { } private Long getFirstSubKeyWithPassphrase(long masterKeyId, ContentResolver resolver) { - Cursor cursor = resolver.query( - KeychainContract.Keys.buildKeysUri(masterKeyId), new String[]{ - Keys.KEY_ID, - Keys.HAS_SECRET, - }, Keys.HAS_SECRET + " != 0", null, null); - try { - if (cursor != null) { - while(cursor.moveToNext()) { - SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); - switch (secretKeyType) { - case PASSPHRASE_EMPTY: - case DIVERT_TO_CARD: - case UNAVAILABLE: - return null; - case GNU_DUMMY: - continue; - default: { - return cursor.getLong(0); - } - } + KeyRepository keyRepository = KeyRepository.create(requireContext()); + for (SubKey subKey : keyRepository.getSubKeysByMasterKeyId(masterKeyId)) { + switch (subKey.has_secret()) { + case PASSPHRASE_EMPTY: + case DIVERT_TO_CARD: + case UNAVAILABLE: + return null; + case GNU_DUMMY: + continue; + default: { + return subKey.key_id(); } } - } finally { - if (cursor != null) { - cursor.close(); - } } return null; } 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 f8431bf3f..f1446a26d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -20,11 +20,11 @@ package org.sufficientlysecure.keychain.ui; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.regex.Pattern; import android.app.Activity; import android.content.Intent; -import android.database.Cursor; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; @@ -43,6 +43,7 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; @@ -50,7 +51,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; @@ -410,31 +410,20 @@ public class CreateKeyFinalFragment extends Fragment { private void moveToCard(final EditKeyResult saveKeyResult) { CreateKeyActivity activity = (CreateKeyActivity) getActivity(); + KeyRepository keyRepository = KeyRepository.create(getContext()); SaveKeyringParcel.Builder builder; - CachedPublicKeyRing key = (KeyRepository.create(getContext())) - .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); + CachedPublicKeyRing key = keyRepository.getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); try { - builder = SaveKeyringParcel.buildChangeKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); + builder = SaveKeyringParcel.buildChangeKeyringParcel(saveKeyResult.mMasterKeyId, key.getFingerprint()); } catch (PgpKeyNotFoundException e) { Timber.e("Key that should be moved to Security Token not found in database!"); return; } - // define subkeys that should be moved to the card - Cursor cursor = activity.getContentResolver().query( - KeychainContract.Keys.buildKeysUri(builder.getMasterKeyId()), - new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null - ); - try { - while (cursor != null && cursor.moveToNext()) { - long subkeyId = cursor.getLong(0); - builder.addOrReplaceSubkeyChange(SubkeyChange.createMoveToSecurityTokenChange(subkeyId)); - } - } finally { - if (cursor != null) { - cursor.close(); - } + List subKeys = keyRepository.getSubKeysByMasterKeyId(saveKeyResult.mMasterKeyId); + for (SubKey subKey : subKeys) { + builder.addOrReplaceSubkeyChange(SubkeyChange.createMoveToSecurityTokenChange(subKey.key_id())); } // define new PIN and Admin PIN for the card 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 7f28946b1..4a202043c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -59,7 +59,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; import org.sufficientlysecure.keychain.livedata.KeyRingDao; -import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index bff3d5695..1d5eff9fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.ui; +import java.util.List; + +import android.arch.lifecycle.LiveData; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -41,8 +44,11 @@ import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; @@ -62,7 +68,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "data_uri"; private static final int LOADER_ID_UNIFIED = 0; - private static final int LOADER_ID_SUBKEYS = 1; private ListView mSubkeysList; private ListView mSubkeysAddedList; @@ -78,7 +83,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements private long mMasterKeyId; private byte[] mFingerprint; - private boolean mHasSecret; private SaveKeyringParcel.Builder mEditModeSkpBuilder; @Override @@ -148,13 +152,14 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mDataUri = dataUri; // Create an empty adapter we will use to display the loaded data. - mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); + mSubkeysAdapter = new SubkeysAdapter(requireContext()); mSubkeysList.setAdapter(mSubkeysAdapter); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, this); + + setContentShown(false); } // These are the rows that we will retrieve. @@ -178,14 +183,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements PROJECTION, null, null, null); } - case LOADER_ID_SUBKEYS: { - setContentShown(false); - - Uri subkeysUri = KeychainContract.Keys.buildKeysUri(mDataUri); - return new CursorLoader(getActivity(), subkeysUri, - SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); - } - default: return null; } @@ -202,29 +199,29 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements data.moveToFirst(); mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mFingerprint = data.getBlob(INDEX_FINGERPRINT); - break; - } - case LOADER_ID_SUBKEYS: { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mSubkeysAdapter.swapCursor(data); - // TODO: maybe show not before both are loaded! - setContentShown(true); + KeyRepository keyRepository = KeyRepository.create(requireContext()); + LiveData> subKeyLiveData = new GenericLiveData<>(requireContext(), null, + () -> keyRepository.getSubKeysByMasterKeyId(mMasterKeyId)); + subKeyLiveData.observe(this, this::onLoadSubKeys); break; } } } + private void onLoadSubKeys(List subKeys) { + mSubkeysAdapter.setData(subKeys); + setContentShown(true); + } + /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { - mSubkeysAdapter.swapCursor(null); + } @Override @@ -256,7 +253,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeyAddFabLayout.setDisplayedChild(1); mSubkeysAdapter.setEditMode(mEditModeSkpBuilder); - getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); mode.setTitle(R.string.title_edit_subkeys); mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); @@ -281,7 +277,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeysAdapter.setEditMode(null); mSubkeysAddedLayout.setVisibility(View.GONE); mSubkeyAddFabLayout.setDisplayedChild(0); - getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); } }); } @@ -309,7 +304,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements } private void editSubkey(final int position) { - final long keyId = mSubkeysAdapter.getKeyId(position); + final SubKey subKey = mSubkeysAdapter.getItem(position); Handler returnHandler = new Handler() { @Override @@ -320,29 +315,28 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements break; case EditSubkeyDialogFragment.MESSAGE_REVOKE: // toggle - if (mEditModeSkpBuilder.getMutableRevokeSubKeys().contains(keyId)) { - mEditModeSkpBuilder.removeRevokeSubkey(keyId); + if (mEditModeSkpBuilder.getMutableRevokeSubKeys().contains(subKey.key_id())) { + mEditModeSkpBuilder.removeRevokeSubkey(subKey.key_id()); } else { - mEditModeSkpBuilder.addRevokeSubkey(keyId); + mEditModeSkpBuilder.addRevokeSubkey(subKey.key_id()); } break; case EditSubkeyDialogFragment.MESSAGE_STRIP: { - SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); - if (secretKeyType == SecretKeyType.GNU_DUMMY) { + if (subKey.has_secret() == SecretKeyType.GNU_DUMMY) { // Key is already stripped; this is a no-op. break; } - SubkeyChange change = mEditModeSkpBuilder.getSubkeyChange(keyId); + SubkeyChange change = mEditModeSkpBuilder.getSubkeyChange(subKey.key_id()); if (change == null || !change.getDummyStrip()) { - mEditModeSkpBuilder.addOrReplaceSubkeyChange(SubkeyChange.createStripChange(keyId)); + mEditModeSkpBuilder.addOrReplaceSubkeyChange(SubkeyChange.createStripChange(subKey.key_id())); } else { mEditModeSkpBuilder.removeSubkeyChange(change); } break; } } - getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); + mSubkeysAdapter.notifyDataSetChanged(); } }; @@ -360,9 +354,10 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements } private void editSubkeyExpiry(final int position) { - final long keyId = mSubkeysAdapter.getKeyId(position); - final Long creationDate = mSubkeysAdapter.getCreationDate(position); - final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); + SubKey subKey = mSubkeysAdapter.getItem(position); + final long keyId = subKey.key_id(); + final Long creationDate = subKey.creation(); + final Long expiryDate = subKey.expiry(); Handler returnHandler = new Handler() { @Override @@ -375,7 +370,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements SubkeyChange.createFlagsOrExpiryChange(keyId, null, expiry)); break; } - getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); + mSubkeysAdapter.notifyDataSetChanged(); } }; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java index 49a188d96..cf53b113f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java @@ -17,7 +17,7 @@ import eu.davidea.flexibleadapter.items.IFilterable; import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.FlexibleViewHolder; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem.FlexibleKeyItemViewHolder; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java index abf5007a8..3008dd4ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java @@ -10,7 +10,7 @@ import android.content.res.Resources; import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.Key.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; public class FlexibleKeyItemFactory { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index 2d326cba5..3be6e03a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -20,15 +20,14 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.util.Calendar; import java.util.Date; +import java.util.List; import java.util.TimeZone; import android.content.Context; import android.content.res.ColorStateList; -import android.database.Cursor; import android.graphics.PorterDuff; import android.graphics.Typeface; import android.support.annotation.Nullable; -import android.support.v4.widget.CursorAdapter; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -37,121 +36,64 @@ import android.text.style.StyleSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -public class SubkeysAdapter extends CursorAdapter { - private LayoutInflater mInflater; +public class SubkeysAdapter extends BaseAdapter { + private final Context context; + private final LayoutInflater layoutInflater; + + private List data; private SaveKeyringParcel.Builder mSkpBuilder; - private boolean mHasAnySecret; private ColorStateList mDefaultTextColor; - public static final String[] SUBKEYS_PROJECTION = new String[]{ - Keys._ID, - Keys.KEY_ID, - Keys.RANK, - Keys.ALGORITHM, - Keys.KEY_SIZE, - Keys.KEY_CURVE_OID, - Keys.HAS_SECRET, - Keys.CAN_CERTIFY, - Keys.CAN_ENCRYPT, - Keys.CAN_SIGN, - Keys.CAN_AUTHENTICATE, - Keys.IS_REVOKED, - Keys.IS_SECURE, - Keys.CREATION, - Keys.EXPIRY, - Keys.FINGERPRINT - }; - private static final int INDEX_ID = 0; - private static final int INDEX_KEY_ID = 1; - private static final int INDEX_RANK = 2; - private static final int INDEX_ALGORITHM = 3; - private static final int INDEX_KEY_SIZE = 4; - private static final int INDEX_KEY_CURVE_OID = 5; - private static final int INDEX_HAS_SECRET = 6; - private static final int INDEX_CAN_CERTIFY = 7; - private static final int INDEX_CAN_ENCRYPT = 8; - private static final int INDEX_CAN_SIGN = 9; - private static final int INDEX_CAN_AUTHENTICATE = 10; - private static final int INDEX_IS_REVOKED = 11; - private static final int INDEX_IS_SECURE = 12; - private static final int INDEX_CREATION = 13; - private static final int INDEX_EXPIRY = 14; - private static final int INDEX_FINGERPRINT = 15; - - public SubkeysAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - - mInflater = LayoutInflater.from(context); + public SubkeysAdapter(Context context) { + this.context = context; + layoutInflater = LayoutInflater.from(context); } - public long getKeyId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(INDEX_KEY_ID); + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); } - public long getCreationDate(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(INDEX_CREATION); + @Override + public int getCount() { + return data != null ? data.size() : 0; } - public Long getExpiryDate(int position) { - mCursor.moveToPosition(position); - if (mCursor.isNull(INDEX_EXPIRY)) { - return null; + @Override + public SubKey getItem(int position) { + return data.get(position); + } + + @Override + public long getItemId(int position) { + return data.get(position).key_id(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView != null) { + view = convertView; } else { - return mCursor.getLong(INDEX_EXPIRY); - } - } - - public int getAlgorithm(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(INDEX_ALGORITHM); - } - - public int getKeySize(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(INDEX_KEY_SIZE); - } - - public String getCurveOid(int position) { - mCursor.moveToPosition(position); - return mCursor.getString(INDEX_KEY_CURVE_OID); - } - - public SecretKeyType getSecretKeyType(int position) { - mCursor.moveToPosition(position); - return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET)); - } - - @Override - public Cursor swapCursor(Cursor newCursor) { - mHasAnySecret = false; - if (newCursor != null && newCursor.moveToFirst()) { - do { - SecretKeyType hasSecret = SecretKeyType.fromNum(newCursor.getInt(INDEX_HAS_SECRET)); - if (hasSecret.isUsable()) { - mHasAnySecret = true; - break; - } - } while (newCursor.moveToNext()); + view = layoutInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false); } - return super.swapCursor(newCursor); - } + if (mDefaultTextColor == null) { + TextView keyId = view.findViewById(R.id.subkey_item_key_id); + mDefaultTextColor = keyId.getTextColors(); + } - @Override - public void bindView(View view, Context context, Cursor cursor) { TextView vKeyId = view.findViewById(R.id.subkey_item_key_id); TextView vKeyDetails = view.findViewById(R.id.subkey_item_details); TextView vKeyExpiry = view.findViewById(R.id.subkey_item_expiry); @@ -166,19 +108,20 @@ public class SubkeysAdapter extends CursorAdapter { ImageView deleteImage = view.findViewById(R.id.subkey_item_delete_button); deleteImage.setVisibility(View.GONE); - long keyId = cursor.getLong(INDEX_KEY_ID); - vKeyId.setText(KeyFormattingUtils.beautifyKeyId(keyId)); + SubKey subKey = getItem(position); + + vKeyId.setText(KeyFormattingUtils.beautifyKeyId(subKey.key_id())); // may be set with additional "stripped" later on SpannableStringBuilder algorithmStr = new SpannableStringBuilder(); algorithmStr.append(KeyFormattingUtils.getAlgorithmInfo( context, - cursor.getInt(INDEX_ALGORITHM), - cursor.getInt(INDEX_KEY_SIZE), - cursor.getString(INDEX_KEY_CURVE_OID) + subKey.algorithm(), + subKey.key_size(), + subKey.key_curve_oid() )); - SubkeyChange change = mSkpBuilder != null ? mSkpBuilder.getSubkeyChange(keyId) : null; + SubkeyChange change = mSkpBuilder != null ? mSkpBuilder.getSubkeyChange(subKey.key_id()) : null; if (change != null && (change.getDummyStrip() || change.getMoveKeyToSecurityToken())) { if (change.getDummyStrip()) { algorithmStr.append(", "); @@ -197,7 +140,7 @@ public class SubkeysAdapter extends CursorAdapter { algorithmStr.append(boldDivert); } } else { - switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) { + switch (subKey.has_secret()) { case GNU_DUMMY: algorithmStr.append(", "); algorithmStr.append(context.getString(R.string.key_stripped)); @@ -218,7 +161,7 @@ public class SubkeysAdapter extends CursorAdapter { } vKeyDetails.setText(algorithmStr, TextView.BufferType.SPANNABLE); - boolean isMasterKey = cursor.getInt(INDEX_RANK) == 0; + boolean isMasterKey = subKey.rank() == 0; if (isMasterKey) { vKeyId.setTypeface(null, Typeface.BOLD); } else { @@ -226,22 +169,21 @@ public class SubkeysAdapter extends CursorAdapter { } // Set icons according to properties - vCertifyIcon.setVisibility(cursor.getInt(INDEX_CAN_CERTIFY) != 0 ? View.VISIBLE : View.GONE); - vEncryptIcon.setVisibility(cursor.getInt(INDEX_CAN_ENCRYPT) != 0 ? View.VISIBLE : View.GONE); - vSignIcon.setVisibility(cursor.getInt(INDEX_CAN_SIGN) != 0 ? View.VISIBLE : View.GONE); - vAuthenticateIcon.setVisibility(cursor.getInt(INDEX_CAN_AUTHENTICATE) != 0 ? View.VISIBLE : View.GONE); + vCertifyIcon.setVisibility(subKey.can_certify() ? View.VISIBLE : View.GONE); + vEncryptIcon.setVisibility(subKey.can_encrypt() ? View.VISIBLE : View.GONE); + vSignIcon.setVisibility(subKey.can_sign() ? View.VISIBLE : View.GONE); + vAuthenticateIcon.setVisibility(subKey.can_authenticate() ? View.VISIBLE : View.GONE); - boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - boolean isSecure = cursor.getInt(INDEX_IS_SECURE) > 0; + boolean isRevoked = subKey.is_revoked(); Date expiryDate = null; - if (!cursor.isNull(INDEX_EXPIRY)) { - expiryDate = new Date(cursor.getLong(INDEX_EXPIRY) * 1000); + if (subKey.expires()) { + expiryDate = new Date(subKey.expiry() * 1000); } // for edit key if (mSkpBuilder != null) { - boolean revokeThisSubkey = (mSkpBuilder.getMutableRevokeSubKeys().contains(keyId)); + boolean revokeThisSubkey = (mSkpBuilder.getMutableRevokeSubKeys().contains(subKey.key_id())); if (revokeThisSubkey) { if (!isRevoked) { @@ -249,7 +191,7 @@ public class SubkeysAdapter extends CursorAdapter { } } - SaveKeyringParcel.SubkeyChange subkeyChange = mSkpBuilder.getSubkeyChange(keyId); + SaveKeyringParcel.SubkeyChange subkeyChange = mSkpBuilder.getSubkeyChange(subKey.key_id()); if (subkeyChange != null) { if (subkeyChange.getExpiry() == null || subkeyChange.getExpiry() == 0L) { expiryDate = null; @@ -280,37 +222,37 @@ public class SubkeysAdapter extends CursorAdapter { } // if key is expired or revoked... - boolean isInvalid = isRevoked || isExpired || !isSecure; + boolean isInvalid = isRevoked || isExpired || !subKey.is_secure(); if (isInvalid) { vStatus.setVisibility(View.VISIBLE); vCertifyIcon.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vSignIcon.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vEncryptIcon.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vAuthenticateIcon.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); if (isRevoked) { vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); } else if (isExpired) { vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); - } else if (!isSecure) { + } else if (!subKey.is_secure()) { vStatus.setImageResource(R.drawable.status_signature_invalid_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.key_flag_gray), + context.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); } } else { @@ -328,36 +270,20 @@ public class SubkeysAdapter extends CursorAdapter { vKeyId.setEnabled(!isInvalid); vKeyDetails.setEnabled(!isInvalid); vKeyExpiry.setEnabled(!isInvalid); - } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.view_key_adv_subkey_item, null); - if (mDefaultTextColor == null) { - TextView keyId = view.findViewById(R.id.subkey_item_key_id); - mDefaultTextColor = keyId.getTextColors(); - } return view; } // Disable selection of items, http://stackoverflow.com/a/4075045 @Override public boolean areAllItemsEnabled() { - if (mSkpBuilder == null) { - return false; - } else { - return super.areAllItemsEnabled(); - } + return mSkpBuilder != null && super.areAllItemsEnabled(); } // Disable selection of items, http://stackoverflow.com/a/4075045 @Override public boolean isEnabled(int position) { - if (mSkpBuilder == null) { - return false; - } else { - return super.isEnabled(position); - } + return mSkpBuilder != null && super.isEnabled(position); } /** Set this adapter into edit mode. This mode displays additional info for @@ -372,6 +298,7 @@ public class SubkeysAdapter extends CursorAdapter { */ public void setEditMode(@Nullable SaveKeyringParcel.Builder builder) { mSkpBuilder = builder; + notifyDataSetChanged(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java index 8caf8b261..5ea41d801 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java @@ -24,97 +24,59 @@ import java.util.Comparator; import java.util.Date; import java.util.List; -import android.content.ContentResolver; import android.content.Context; -import android.database.Cursor; import android.support.annotation.NonNull; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import timber.log.Timber; +import org.sufficientlysecure.keychain.provider.KeyRepository; public class SubkeyStatusDao { - public static final String[] PROJECTION = new String[] { - Keys.KEY_ID, - Keys.CREATION, - Keys.CAN_CERTIFY, - Keys.CAN_SIGN, - Keys.CAN_ENCRYPT, - Keys.HAS_SECRET, - Keys.EXPIRY, - Keys.IS_REVOKED, - Keys.ALGORITHM, - Keys.KEY_SIZE, - Keys.KEY_CURVE_OID - }; - private static final int INDEX_KEY_ID = 0; - private static final int INDEX_CREATION = 1; - private static final int INDEX_CAN_CERTIFY = 2; - private static final int INDEX_CAN_SIGN = 3; - private static final int INDEX_CAN_ENCRYPT = 4; - private static final int INDEX_HAS_SECRET = 5; - private static final int INDEX_EXPIRY = 6; - private static final int INDEX_IS_REVOKED = 7; - private static final int INDEX_ALGORITHM = 8; - private static final int INDEX_KEY_SIZE = 9; - private static final int INDEX_KEY_CURVE_OID = 10; - - - private final ContentResolver contentResolver; + private final KeyRepository keyRepository; public static SubkeyStatusDao getInstance(Context context) { - ContentResolver contentResolver = context.getContentResolver(); - return new SubkeyStatusDao(contentResolver); + KeyRepository keyRepository = KeyRepository.create(context); + return new SubkeyStatusDao(keyRepository); } - private SubkeyStatusDao(ContentResolver contentResolver) { - this.contentResolver = contentResolver; + private SubkeyStatusDao(KeyRepository keyRepository) { + this.keyRepository = keyRepository; } KeySubkeyStatus getSubkeyStatus(long masterKeyId, Comparator comparator) { - Cursor cursor = contentResolver.query(Keys.buildKeysUri(masterKeyId), PROJECTION, null, null, null); - if (cursor == null) { - Timber.e("Error loading key items!"); + SubKeyItem keyCertify = null; + ArrayList keysSign = new ArrayList<>(); + ArrayList keysEncrypt = new ArrayList<>(); + for (SubKey subKey : keyRepository.getSubKeysByMasterKeyId(masterKeyId)) { + SubKeyItem ski = new SubKeyItem(masterKeyId, subKey); + + if (ski.mKeyId == masterKeyId) { + keyCertify = ski; + } + + if (ski.mCanSign) { + keysSign.add(ski); + } + if (ski.mCanEncrypt) { + keysEncrypt.add(ski); + } + } + + if (keyCertify == null) { + if (!keysSign.isEmpty() || !keysEncrypt.isEmpty()) { + throw new IllegalStateException("Certification key can't be missing for a key that hasn't been deleted!"); + } return null; } - try { - SubKeyItem keyCertify = null; - ArrayList keysSign = new ArrayList<>(); - ArrayList keysEncrypt = new ArrayList<>(); - while (cursor.moveToNext()) { - SubKeyItem ski = new SubKeyItem(masterKeyId, cursor); + Collections.sort(keysSign, comparator); + Collections.sort(keysEncrypt, comparator); - if (ski.mKeyId == masterKeyId) { - keyCertify = ski; - } - - if (ski.mCanSign) { - keysSign.add(ski); - } - if (ski.mCanEncrypt) { - keysEncrypt.add(ski); - } - } - - if (keyCertify == null) { - if (!keysSign.isEmpty() || !keysEncrypt.isEmpty()) { - throw new IllegalStateException("Certification key can't be missing for a key that hasn't been deleted!"); - } - return null; - } - - Collections.sort(keysSign, comparator); - Collections.sort(keysEncrypt, comparator); - - return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt); - } finally { - cursor.close(); - } + return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt); } public static class KeySubkeyStatus { @@ -131,7 +93,6 @@ public class SubkeyStatusDao { } public static class SubKeyItem { - final int mPosition; final long mKeyId; final Date mCreation; public final SecretKeyType mSecretKeyType; @@ -140,25 +101,23 @@ public class SubkeyStatusDao { final boolean mCanCertify, mCanSign, mCanEncrypt; public final KeySecurityProblem mSecurityProblem; - SubKeyItem(long masterKeyId, Cursor cursor) { - mPosition = cursor.getPosition(); + SubKeyItem(long masterKeyId, SubKey subKey) { + mKeyId = subKey.key_id(); + mCreation = new Date(subKey.creation() * 1000); - mKeyId = cursor.getLong(INDEX_KEY_ID); - mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); + mSecretKeyType = subKey.has_secret(); - mSecretKeyType = SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET)); - - mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - mExpiry = cursor.isNull(INDEX_EXPIRY) ? null : new Date(cursor.getLong(INDEX_EXPIRY) * 1000); + mIsRevoked = subKey.is_revoked(); + mExpiry = subKey.expiry() == null ? null : new Date(subKey.expiry() * 1000); mIsExpired = mExpiry != null && mExpiry.before(new Date()); - mCanCertify = cursor.getInt(INDEX_CAN_CERTIFY) > 0; - mCanSign = cursor.getInt(INDEX_CAN_SIGN) > 0; - mCanEncrypt = cursor.getInt(INDEX_CAN_ENCRYPT) > 0; + mCanCertify = subKey.can_certify(); + mCanSign = subKey.can_sign(); + mCanEncrypt = subKey.can_encrypt(); - int algorithm = cursor.getInt(INDEX_ALGORITHM); - Integer bitStrength = cursor.isNull(INDEX_KEY_SIZE) ? null : cursor.getInt(INDEX_KEY_SIZE); - String curveOid = cursor.getString(INDEX_KEY_CURVE_OID); + int algorithm = subKey.algorithm(); + Integer bitStrength = subKey.key_size(); + String curveOid = subKey.key_curve_oid(); mSecurityProblem = PgpSecurityConstants.getKeySecurityProblem( masterKeyId, mKeyId, algorithm, bitStrength, curveOid); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index 4937ace06..f69194653 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -1,19 +1,20 @@ import java.lang.Boolean; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; CREATE TABLE IF NOT EXISTS keys ( master_key_id INTEGER NOT NULL, rank INTEGER NOT NULL, key_id INTEGER NOT NULL, - key_size INTEGER, + key_size INTEGER AS Integer, key_curve_oid TEXT, - algorithm INTEGER NOT NULL, + algorithm INTEGER AS Integer NOT NULL, fingerprint BLOB NOT NULL, can_certify INTEGER AS Boolean NOT NULL, can_sign INTEGER AS Boolean NOT NULL, can_encrypt INTEGER AS Boolean NOT NULL, can_authenticate INTEGER AS Boolean NOT NULL, is_revoked INTEGER AS Boolean NOT NULL, - has_secret INTEGER AS Boolean NOT NULL, + has_secret INTEGER AS SecretKeyType NOT NULL, is_secure INTEGER AS Boolean NOT NULL, creation INTEGER NOT NULL, expiry INTEGER, @@ -34,4 +35,15 @@ SELECT keys.master_key_id, MIN(user_packets.rank), user_packets.name, user_packe LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id ) WHERE keys.rank = 0 GROUP BY keys.master_key_id - ORDER BY has_secret DESC, user_packets.name COLLATE NOCASE ASC; \ No newline at end of file + ORDER BY has_secret DESC, user_packets.name COLLATE NOCASE ASC; + +selectSubkeysByMasterKeyId: +SELECT * + FROM keys + WHERE master_key_id = ? + ORDER BY rank ASC; + +selectSecretKeyType: +SELECT has_secret + FROM keys + WHERE key_id = ?; \ No newline at end of file From d57a409fac939881088cf6aee7115bfde1710553 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 22 Jun 2018 19:58:17 +0200 Subject: [PATCH 029/124] extract select by signer from KeychainProvider --- .../keychain/livedata/KeyRingDao.java | 41 ------------ .../keychain/operations/BaseOperation.java | 14 ++-- .../keychain/provider/KeyRepository.java | 30 ++++++++- .../provider/KeyWritableRepository.java | 64 +++++++------------ .../keychain/provider/KeychainContract.java | 7 -- .../keychain/provider/KeychainDatabase.java | 3 +- .../keychain/provider/KeychainProvider.java | 24 +------ .../keychain/ui/DecryptFragment.java | 3 +- .../keychain/ui/DeleteKeyDialogActivity.java | 3 +- .../keychain/ui/KeyListFragment.java | 8 +-- .../keychain/KeyRingsPublic.sq | 4 ++ .../keychain/KeySignatures.sq | 7 ++ .../org/sufficientlysecure/keychain/Keys.sq | 7 +- 13 files changed, 87 insertions(+), 128 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java deleted file mode 100644 index 98298a094..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyRingDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.sufficientlysecure.keychain.livedata; - - -import java.util.ArrayList; -import java.util.List; - -import android.arch.persistence.db.SupportSQLiteDatabase; -import android.content.Context; -import android.database.Cursor; - -import com.squareup.sqldelight.SqlDelightQuery; -import org.sufficientlysecure.keychain.model.SubKey; -import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; - - -public class KeyRingDao { - private final SupportSQLiteDatabase db; - - public static KeyRingDao getInstance(Context context) { - KeychainDatabase keychainDatabase = new KeychainDatabase(context); - - return new KeyRingDao(keychainDatabase.getWritableDatabase()); - } - - private KeyRingDao(SupportSQLiteDatabase writableDatabase) { - this.db = writableDatabase; - } - - public List getUnifiedKeyInfo() { - SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); - List result = new ArrayList<>(); - try (Cursor cursor = db.query(query)) { - while (cursor.moveToNext()) { - UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); - result.add(unifiedKeyInfo); - } - } - return result; - } -} 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 3559ea2a4..3e77ef6f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -30,7 +30,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; @@ -113,15 +112,14 @@ public abstract class BaseOperation implements PassphraseC @Override public Passphrase getCachedPassphrase(long subKeyId) throws NoSecretKeyException { - try { - if (subKeyId != key.symmetric) { - long masterKeyId = mKeyRepository.getMasterKeyId(subKeyId); - return getCachedPassphrase(masterKeyId, subKeyId); + if (subKeyId != key.symmetric) { + Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(subKeyId); + if (masterKeyId == null) { + throw new PassphraseCacheInterface.NoSecretKeyException(); } - return getCachedPassphrase(key.symmetric, key.symmetric); - } catch (NotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); + return getCachedPassphrase(masterKeyId, subKeyId); } + return getCachedPassphrase(key.symmetric, key.symmetric); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index ce89cc977..aa51707f4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -33,6 +33,7 @@ import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -172,7 +173,7 @@ public class KeyRepository extends AbstractDao { return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); } - public long getMasterKeyId(long subKeyId) throws NotFoundException { + public long getMasterKeyIdBySubKeyId(long subKeyId) throws NotFoundException { return (Long) getGenericData(KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId), KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER); } @@ -238,6 +239,33 @@ public class KeyRepository extends AbstractDao { } } + public List getAllMasterKeyIds() { + SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds(); + return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); + } + + public List getMasterKeyIdsBySigner(List signerMasterKeyIds) { + long[] signerKeyIds = new long[signerMasterKeyIds.size()]; + int i = 0; + for (Long signerKeyId : signerMasterKeyIds) { + signerKeyIds[i++] = signerKeyId; + } + SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); + return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); + } + + public List getUnifiedKeyInfo() { + SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); + List result = new ArrayList<>(); + try (Cursor cursor = getReadableDb().query(query)) { + while (cursor.moveToNext()) { + UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); + result.add(unifiedKeyInfo); + } + } + return result; + } + public List getUserIds(long... masterKeyIds) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds); return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 7cabe3de5..008af6e31 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -1013,30 +1013,18 @@ public class KeyWritableRepository extends KeyRepository { log.add(LogType.MSG_TRUST, 0); - Cursor cursor; Preferences preferences = Preferences.getPreferences(context); boolean isTrustDbInitialized = preferences.isKeySignaturesTableInitialized(); + + List masterKeyIds; if (!isTrustDbInitialized) { log.add(LogType.MSG_TRUST_INITIALIZE, 1); - cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), - new String[] { KeyRings.MASTER_KEY_ID }, null, null, null); + masterKeyIds = getAllMasterKeyIds(); } else { - String[] signerMasterKeyIdStrings = new String[signerMasterKeyIds.size()]; - int i = 0; - for (Long masterKeyId : signerMasterKeyIds) { - log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId)); - signerMasterKeyIdStrings[i++] = Long.toString(masterKeyId); - } - - cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsFilterBySigner(), - new String[] { KeyRings.MASTER_KEY_ID }, null, signerMasterKeyIdStrings, null); + masterKeyIds = getMasterKeyIdsBySigner(signerMasterKeyIds); } - if (cursor == null) { - throw new IllegalStateException(); - } - - int totalKeys = cursor.getCount(); + int totalKeys = masterKeyIds.size(); int processedKeys = 0; if (totalKeys == 0) { @@ -1046,34 +1034,30 @@ public class KeyWritableRepository extends KeyRepository { log.add(LogType.MSG_TRUST_COUNT, 1, totalKeys); } - try { - while (cursor.moveToNext()) { - try { - long masterKeyId = cursor.getLong(0); + for (long masterKeyId : masterKeyIds) { + try { + log.add(LogType.MSG_TRUST_KEY, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId)); - byte[] pubKeyData = loadPublicKeyRingData(masterKeyId); - UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData); + byte[] pubKeyData = loadPublicKeyRingData(masterKeyId); + UncachedKeyRing uncachedKeyRing = UncachedKeyRing.decodeFromData(pubKeyData); - clearLog(); - SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true); + clearLog(); + SaveKeyringResult result = savePublicKeyRing(uncachedKeyRing, true); - log.add(result, 1); - progress.setProgress(processedKeys++, totalKeys); - } catch (NotFoundException | PgpGeneralException | IOException e) { - Timber.e(e, "Error updating trust database"); - return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log); - } + log.add(result, 1); + progress.setProgress(processedKeys++, totalKeys); + } catch (NotFoundException | PgpGeneralException | IOException e) { + Timber.e(e, "Error updating trust database"); + return new UpdateTrustResult(UpdateTrustResult.RESULT_ERROR, log); } - - if (!isTrustDbInitialized) { - preferences.setKeySignaturesTableInitialized(); - } - - log.add(LogType.MSG_TRUST_OK, 1); - return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log); - } finally { - cursor.close(); } + + if (!isTrustDbInitialized) { + preferences.setKeySignaturesTableInitialized(); + } + + log.add(LogType.MSG_TRUST_OK, 1); + return new UpdateTrustResult(UpdateTrustResult.RESULT_OK, log); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 0a39dc397..7c07174d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -104,9 +104,6 @@ public class KeychainContract { public static final String PATH_BY_SUBKEY = "subkey"; public static final String PATH_BY_USER_ID = "user_id"; - public static final String PATH_FILTER = "filter"; - public static final String PATH_BY_SIGNER = "signer"; - public static final String PATH_PUBLIC = "public"; public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_KEYS = "keys"; @@ -170,10 +167,6 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_FIND) .appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build(); } - - public static Uri buildUnifiedKeyRingsFilterBySigner() { - return CONTENT_URI.buildUpon().appendPath(PATH_FILTER).appendPath(PATH_BY_SIGNER).build(); - } } public static class KeyRingData implements KeyRingsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 5e0e8e2b6..2363bffe9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.CertsModel; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeyMetadataModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel; +import org.sufficientlysecure.keychain.KeySignaturesModel; import org.sufficientlysecure.keychain.UserPacketsModel; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.model.Certification; @@ -199,7 +200,7 @@ public class KeychainDatabase { db.execSQL(UserPacketsModel.CREATE_TABLE); db.execSQL(CertsModel.CREATE_TABLE); db.execSQL(KeyMetadataModel.CREATE_TABLE); - db.execSQL(CREATE_KEY_SIGNATURES); + db.execSQL(KeySignaturesModel.CREATE_TABLE); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(AutocryptPeersModel.CREATE_TABLE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 40fae66eb..1e4534125 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -65,7 +65,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; private static final int KEY_RINGS_FIND_BY_USER_ID = 402; - private static final int KEY_RINGS_FILTER_BY_SIGNER = 403; private static final int KEY_SIGNATURES = 700; @@ -108,9 +107,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_USER_ID + "/*", KEY_RINGS_FIND_BY_USER_ID); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_FILTER + "/" + KeychainContract.PATH_BY_SIGNER, - KEY_RINGS_FILTER_BY_SIGNER); /* * list key_ring specifics @@ -191,8 +187,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case KEY_RINGS_UNIFIED: case KEY_RINGS_FIND_BY_EMAIL: case KEY_RINGS_FIND_BY_SUBKEY: - case KEY_RINGS_FIND_BY_USER_ID: - case KEY_RINGS_FILTER_BY_SIGNER: { + case KEY_RINGS_FIND_BY_USER_ID: { HashMap projectionMap = new HashMap<>(); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); @@ -356,23 +351,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } break; } - case KEY_RINGS_FILTER_BY_SIGNER: { - StringBuilder signerKeyIds = new StringBuilder(); - signerKeyIds.append(selectionArgs[0]); - for (int i = 1; i < selectionArgs.length; i++) { - signerKeyIds.append(',').append(selectionArgs[i]); - } - - qb.appendWhere(" AND EXISTS (SELECT 1 FROM " + Tables.KEY_SIGNATURES + " WHERE " + - Tables.KEY_SIGNATURES + "." + KeySignatures.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + - " AND " + - Tables.KEY_SIGNATURES + "." + KeySignatures.SIGNER_KEY_ID + " IN (" + signerKeyIds + ")" + - ")"); - - selection = null; - selectionArgs = null; - break; - } case KEY_RINGS_FIND_BY_EMAIL: case KEY_RINGS_FIND_BY_USER_ID: { String chunks[] = uri.getLastPathSegment().split(" *, *"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 0069278d5..fe9eaf710 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -196,7 +196,8 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. try { Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class); - long masterKeyId = KeyRepository.create(getContext()).getCachedPublicKeyRing( + KeyRepository keyRepository = KeyRepository.create(requireContext()); + long masterKeyId = keyRepository.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId) ).getMasterKeyId(); viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java index 7d13280ce..d6afada2f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java @@ -86,7 +86,8 @@ public class DeleteKeyDialogActivity extends FragmentActivity { if (mMasterKeyIds.length == 1 && mHasSecret) { // if mMasterKeyIds.length == 0 we let the DeleteOperation respond try { - HashMap data = KeyRepository.create(this).getUnifiedData( + KeyRepository keyRepository = KeyRepository.create(this); + HashMap data = keyRepository.getUnifiedData( mMasterKeyIds[0], new String[]{ KeychainContract.KeyRings.NAME, KeychainContract.KeyRings.IS_REVOKED 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 4a202043c..357eb44fd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -58,11 +58,11 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager; -import org.sufficientlysecure.keychain.livedata.KeyRingDao; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; 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.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; @@ -256,18 +256,18 @@ public class KeyListFragment extends RecyclerFragment> { - private final KeyRingDao keyRingDao; + private final KeyRepository keyRepository; private FlexibleKeyItemFactory flexibleKeyItemFactory; KeyListLiveData(@NonNull Context context) { super(context, KeyRings.CONTENT_URI); - keyRingDao = KeyRingDao.getInstance(context.getApplicationContext()); + keyRepository = KeyRepository.create(context.getApplicationContext()); flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources()); } @Override protected List asyncLoadData() { - List unifiedKeyInfo = keyRingDao.getUnifiedKeyInfo(); + List unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(); return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo); } } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq index 95debd60f..33e3b3f0d 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeyRingsPublic.sq @@ -3,6 +3,10 @@ CREATE TABLE IF NOT EXISTS keyrings_public ( key_ring_data BLOB NULL ); +selectAllMasterKeyIds: +SELECT master_key_id + FROM keyrings_public; + selectByMasterKeyId: SELECT * FROM keyrings_public diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq new file mode 100644 index 000000000..1b61dadf3 --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS key_signatures ( + master_key_id INTEGER NOT NULL, + signer_key_id INTEGER NOT NULL, + PRIMARY KEY(master_key_id, signer_key_id), + FOREIGN KEY(master_key_id) REFERENCES + keyrings_public(master_key_id) ON DELETE CASCADE +); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index f69194653..c147c0d1f 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -46,4 +46,9 @@ SELECT * selectSecretKeyType: SELECT has_secret FROM keys - WHERE key_id = ?; \ No newline at end of file + WHERE key_id = ?; + +-- TODO move to KeySignatures.sq +selectMasterKeyIdsBySigner: +SELECT master_key_id + FROM key_signatures WHERE signer_key_id IN ?; \ No newline at end of file From 2d1ff8cdcc703686b9e857129bf51cdc0ff79f1f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 00:02:05 +0200 Subject: [PATCH 030/124] use LiveData in ViewKeyAdvActivity --- .../keychain/model/SubKey.java | 4 + .../keychain/provider/KeyRepository.java | 17 +- .../keychain/provider/KeychainContract.java | 8 - .../remote/ApiPendingIntentFactory.java | 3 +- .../keychain/ui/DecryptFragment.java | 3 +- .../keychain/ui/DecryptListFragment.java | 3 +- .../keychain/ui/KeyListFragment.java | 5 +- .../keychain/ui/UploadKeyActivity.java | 34 +- .../keychain/ui/ViewKeyAdvActivity.java | 314 +++++++--------- .../keychain/ui/ViewKeyAdvShareFragment.java | 338 +++++------------- .../ui/ViewKeyAdvSubkeysFragment.java | 176 ++------- .../ui/ViewKeyAdvUserIdsFragment.java | 174 ++------- .../keychain/ui/ViewKeyKeybaseFragment.java | 34 +- .../ui/adapter/ImportKeysAdapter.java | 15 +- .../keychain/ui/keyview/ViewKeyActivity.java | 102 +++--- .../linked/LinkedIdCreateGithubFragment.java | 9 +- .../ui/token/ManageSecurityTokenFragment.java | 6 +- .../keychain/util/ContactHelper.java | 4 +- .../org/sufficientlysecure/keychain/Keys.sq | 19 +- 19 files changed, 409 insertions(+), 859 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 36102c73c..5505c39c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -54,5 +54,9 @@ public abstract class SubKey implements KeysModel { } return autocryptPackageNames; } + + public boolean has_auth_key() { + return has_auth_key_int() != 0; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index aa51707f4..a88937e11 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -254,16 +254,19 @@ public class KeyRepository extends AbstractDao { return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); } - public List getUnifiedKeyInfo() { - SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); - List result = new ArrayList<>(); + public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) { + SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId); try (Cursor cursor = getReadableDb().query(query)) { - while (cursor.moveToNext()) { - UnifiedKeyInfo unifiedKeyInfo = SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); - result.add(unifiedKeyInfo); + if (cursor.moveToNext()) { + return SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); } + return null; } - return result; + } + + public List getAllUnifiedKeyInfo() { + SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); + return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); } public List getUserIds(long... masterKeyIds) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 7c07174d8..661d8efab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -135,14 +135,6 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); } - public static Uri buildGenericKeyRingUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).build(); - } - - public static Uri buildGenericKeyRingUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).build(); - } - public static Uri buildUnifiedKeyRingUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)) .appendPath(PATH_UNIFIED).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 4f135f14f..7de402e69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -130,8 +130,7 @@ public class ApiPendingIntentFactory { } PendingIntent createShowKeyPendingIntent(Intent data, long masterKeyId) { - Intent intent = new Intent(mContext, ViewKeyActivity.class); - intent.setData(KeychainContract.KeyRings.buildGenericKeyRingUri(masterKeyId)); + Intent intent = ViewKeyActivity.getViewKeyActivityIntent(mContext, masterKeyId); return createInternal(data, intent); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index fe9eaf710..8387ff951 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -195,12 +195,11 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. private void showKey(long keyId) { try { - Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class); KeyRepository keyRepository = KeyRepository.create(requireContext()); long masterKeyId = keyRepository.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId) ).getMasterKeyId(); - viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + Intent viewKeyIntent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), masterKeyId); startActivity(viewKeyIntent); } catch (PgpKeyNotFoundException e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index b1aaecc20..8e2e9604f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -1043,8 +1043,7 @@ public class DecryptListFragment if (activity == null) { return; } - Intent intent = new Intent(activity, ViewKeyActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); + Intent intent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), keyId); activity.startActivity(intent); } }); 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 357eb44fd..871a7335b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -267,7 +267,7 @@ public class KeyListFragment extends RecyclerFragment asyncLoadData() { - List unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(); + List unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo(); return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo); } } @@ -421,8 +421,7 @@ public class KeyListFragment extends RecyclerFragment mUploadOpHelper; @@ -82,14 +75,6 @@ public class UploadKeyActivity extends BaseActivity uploadKey(); } }); - - mDataUri = getIntent().getData(); - if (mDataUri == null) { - Timber.e("Intent data missing. Should be Uri of key!"); - finish(); - return; - } - } private String[] getKeyserversArray() { @@ -126,19 +111,6 @@ public class UploadKeyActivity extends BaseActivity mUploadOpHelper.cryptoOperation(); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - Intent viewIntent = NavUtils.getParentActivityIntent(this); - viewIntent.setData(KeychainContract.KeyRings.buildGenericKeyRingUri(mDataUri)); - NavUtils.navigateUpTo(this, viewIntent); - return true; - } - } - return super.onOptionsItemSelected(item); - } - @Override public UploadKeyringParcel createOperationInput() { long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 55fc4da39..b79645823 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -18,16 +18,19 @@ package org.sufficientlysecure.keychain.ui; +import java.util.List; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Transformations; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Context; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; -import android.provider.ContactsContract; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; +import android.support.annotation.StringRes; +import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.ActionMode; @@ -37,83 +40,154 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewPropertyAnimator; import android.view.animation.OvershootInterpolator; -import android.widget.Toast; import com.astuetz.PagerSlidingTabStrip; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ContactHelper; -import timber.log.Timber; -public class ViewKeyAdvActivity extends BaseActivity implements - LoaderCallbacks, OnPageChangeListener { - - KeyRepository mKeyRepository; - - protected Uri mDataUri; - +public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeListener { + public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; public static final String EXTRA_SELECTED_TAB = "selected_tab"; - public static final int TAB_START = 0; - public static final int TAB_SHARE = 1; - public static final int TAB_IDENTITIES = 2; - public static final int TAB_SUBKEYS = 3; - public static final int TAB_CERTS = 4; + + KeyRepository keyRepository; // view private ViewPager mViewPager; private PagerSlidingTabStrip mSlidingTabLayout; - private static final int LOADER_ID_UNIFIED = 0; private ActionMode mActionMode; - private boolean mHasSecret; - private PagerTabStripAdapter mTabAdapter; + private boolean hasSecret; private boolean mActionIconShown; - private boolean[] mTabsWithActionMode; + + enum ViewKeyAdvTab { + START(ViewKeyAdvStartFragment.class, R.string.key_view_tab_start, false), + SHARE(ViewKeyAdvShareFragment.class, R.string.key_view_tab_share, false), + IDENTITIES(ViewKeyAdvUserIdsFragment.class, R.string.section_user_ids, true), + SUBKEYS(ViewKeyAdvSubkeysFragment.class, R.string.key_view_tab_keys, true); + + public final Class fragmentClass; + public final int titleRes; + public final boolean hasActionMode; + + ViewKeyAdvTab(Class fragmentClass, @StringRes int titleRes, boolean hasActionMode) { + this.titleRes = titleRes; + this.fragmentClass = fragmentClass; + this.hasActionMode = hasActionMode; + } + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); + setFullScreenDialogClose(v -> finish()); - mKeyRepository = KeyRepository.create(this); + keyRepository = KeyRepository.create(this); mViewPager = findViewById(R.id.pager); mSlidingTabLayout = findViewById(R.id.sliding_tab_layout); - mDataUri = getIntent().getData(); - if (mDataUri == null) { - Timber.e("Data missing. Should be uri of key!"); - finish(); + if (!getIntent().hasExtra(EXTRA_MASTER_KEY_ID)) { + throw new IllegalArgumentException("Missing required extra master_key_id"); + } + + ViewKeyAdvViewModel viewModel = ViewModelProviders.of(this).get(ViewKeyAdvViewModel.class); + viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L)); + viewModel.getUnifiedKeyInfoLiveData(getApplicationContext()).observe(this, this::onLoadUnifiedKeyInfo); + + initTabs(); + } + + public static class ViewKeyAdvViewModel extends ViewModel { + private Long masterKeyId; + private LiveData unifiedKeyInfoLiveData; + private LiveData> subKeyLiveData; + private LiveData> userIdsLiveData; + + void setMasterKeyId(long masterKeyId) { + if (this.masterKeyId != null) { + throw new IllegalStateException("cannot change masterKeyId once set!"); + } + this.masterKeyId = masterKeyId; + } + + LiveData getUnifiedKeyInfoLiveData(Context context) { + if (masterKeyId == null) { + throw new IllegalStateException("masterKeyId must be set to retrieve this!"); + } + if (unifiedKeyInfoLiveData == null) { + KeyRepository keyRepository = KeyRepository.create(context); + unifiedKeyInfoLiveData = new GenericLiveData<>(context, null, + () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); + } + return unifiedKeyInfoLiveData; + } + + LiveData> getSubkeyLiveData(Context context) { + if (subKeyLiveData == null) { + KeyRepository keyRepository = KeyRepository.create(context); + subKeyLiveData = Transformations.switchMap(getUnifiedKeyInfoLiveData(context), + (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + () -> keyRepository.getSubKeysByMasterKeyId(unifiedKeyInfo.master_key_id()))); + } + return subKeyLiveData; + } + + LiveData> getUserIdLiveData(Context context) { + if (userIdsLiveData == null) { + KeyRepository keyRepository = KeyRepository.create(context); + userIdsLiveData = Transformations.switchMap(getUnifiedKeyInfoLiveData(context), + (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + () -> keyRepository.getUserIds(unifiedKeyInfo.master_key_id()))); + } + return userIdsLiveData; + } + } + + public void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { return; } - if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) { - mDataUri = new ContactHelper(this).dataUriFromContactUri(mDataUri); - if (mDataUri == null) { - Timber.e("Contact Data missing. Should be uri of key!"); - Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); - finish(); - return; - } + + if (unifiedKeyInfo.name() != null) { + setTitle(unifiedKeyInfo.name()); + } else { + setTitle(R.string.user_id_no_name); } - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + String formattedKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(unifiedKeyInfo.master_key_id()); + mToolbar.setSubtitle(formattedKeyId); - initTabs(mDataUri); + hasSecret = unifiedKeyInfo.has_any_secret(); + + // Note: order is important + int color; + if (unifiedKeyInfo.is_revoked() || unifiedKeyInfo.is_expired()) { + color = getResources().getColor(R.color.key_flag_red); + } else if (unifiedKeyInfo.has_any_secret()) { + color = getResources().getColor(R.color.android_green_light); + } else { + if (unifiedKeyInfo.is_verified()) { + color = getResources().getColor(R.color.android_green_light); + } else { + color = getResources().getColor(R.color.key_flag_orange); + } + } + mToolbar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color)); + mSlidingTabLayout.setBackgroundColor(color); + + invalidateOptionsMenu(); } @Override @@ -121,34 +195,13 @@ public class ViewKeyAdvActivity extends BaseActivity implements setContentView(R.layout.view_key_adv_activity); } - private void initTabs(Uri dataUri) { - mTabAdapter = new PagerTabStripAdapter(this); - mViewPager.setAdapter(mTabAdapter); + private void initTabs() { + PagerTabStripAdapter tabAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(tabAdapter); - // keep track which of these are action mode enabled! - mTabsWithActionMode = new boolean[4]; - - mTabAdapter.addTab(ViewKeyAdvStartFragment.class, - null, getString(R.string.key_view_tab_start)); - mTabsWithActionMode[0] = false; - - Bundle shareBundle = new Bundle(); - shareBundle.putParcelable(ViewKeyAdvShareFragment.ARG_DATA_URI, dataUri); - mTabAdapter.addTab(ViewKeyAdvShareFragment.class, - shareBundle, getString(R.string.key_view_tab_share)); - mTabsWithActionMode[1] = false; - - Bundle userIdsBundle = new Bundle(); - userIdsBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri); - mTabAdapter.addTab(ViewKeyAdvUserIdsFragment.class, - userIdsBundle, getString(R.string.section_user_ids)); - mTabsWithActionMode[2] = true; - - Bundle keysBundle = new Bundle(); - keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri); - mTabAdapter.addTab(ViewKeyAdvSubkeysFragment.class, - keysBundle, getString(R.string.key_view_tab_keys)); - mTabsWithActionMode[3] = true; + for (ViewKeyAdvTab tab : ViewKeyAdvTab.values()) { + tabAdapter.addTab(tab.fragmentClass, null, getString(tab.titleRes)); + } // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); @@ -156,108 +209,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements // switch to tab selected by extra Intent intent = getIntent(); - int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, TAB_START); + int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, 0); mViewPager.setCurrentItem(switchToTab); - - } - - // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.NAME, - KeychainContract.KeyRings.EMAIL, - KeychainContract.KeyRings.COMMENT, - }; - - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_USER_ID = 2; - static final int INDEX_IS_REVOKED = 3; - static final int INDEX_IS_EXPIRED = 4; - static final int INDEX_VERIFIED = 5; - static final int INDEX_HAS_ANY_SECRET = 6; - static final int INDEX_FINGERPRINT = 7; - static final int INDEX_NAME = 8; - static final int INDEX_EMAIL = 9; - static final int INDEX_COMMENT = 10; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(this, baseUri, PROJECTION, null, null, null); - } - - default: - return null; - } - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Avoid NullPointerExceptions... - if (data == null || data.getCount() == 0) { - return; - } - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - if (data.moveToFirst()) { - // get name, email, and comment from USER_ID - String name = data.getString(INDEX_NAME); - - if (name != null) { - setTitle(name); - } else { - setTitle(R.string.user_id_no_name); - } - - byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); - - // get key id from MASTER_KEY_ID - long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - String formattedKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(masterKeyId); - getSupportActionBar().setSubtitle(formattedKeyId); - - mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; - boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; - boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; - - // Note: order is important - int color; - if (isRevoked || isExpired) { - color = getResources().getColor(R.color.key_flag_red); - } else if (mHasSecret) { - color = getResources().getColor(R.color.android_green_light); - } else { - if (isVerified) { - color = getResources().getColor(R.color.android_green_light); - } else { - color = getResources().getColor(R.color.key_flag_orange); - } - } - mToolbar.setBackgroundColor(color); - mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color)); - mSlidingTabLayout.setBackgroundColor(color); - - break; - } - } - } - } - - @Override - public void onLoaderReset(Loader loader) { - } @Override @@ -273,8 +226,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements @Override public boolean onCreateOptionsMenu(Menu menu) { - - if (!mHasSecret) { + if (!hasSecret) { return false; } @@ -282,7 +234,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements getMenuInflater().inflate(R.menu.action_mode_edit, menu); final MenuItem vActionModeItem = menu.findItem(R.id.menu_action_mode_edit); - boolean isCurrentActionFragment = mTabsWithActionMode[mViewPager.getCurrentItem()]; + boolean isCurrentActionFragment = ViewKeyAdvTab.values()[mViewPager.getCurrentItem()].hasActionMode; // if the state is as it should be, never mind if (isCurrentActionFragment == mActionIconShown) { @@ -298,7 +250,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements } private void animateMenuItem(final MenuItem vEditSubkeys, final boolean animateShow) { - View actionView = LayoutInflater.from(this).inflate(R.layout.edit_icon, null); vEditSubkeys.setActionView(actionView); actionView.setTranslationX(animateShow ? 150 : 0); @@ -317,7 +268,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements } }); animator.start(); - } @Override 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 f83ecfc88..bf2370ecb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -18,45 +18,50 @@ package org.sufficientlysecure.keychain.ui; +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.security.NoSuchAlgorithmException; + import android.app.Activity; import android.app.ActivityOptions; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Transformations; +import android.arch.lifecycle.ViewModelProviders; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import org.openintents.openpgp.util.OpenPgpUtils; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; -import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; +import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; @@ -65,28 +70,13 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import timber.log.Timber; -import java.io.BufferedWriter; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.security.NoSuchAlgorithmException; - -public class ViewKeyAdvShareFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks { - - public static final String ARG_DATA_URI = "uri"; - +public class ViewKeyAdvShareFragment extends LoaderFragment { private ImageView mQrCode; private CardView mQrCodeLayout; private TextView mFingerprintView; - private static final int LOADER_ID_UNIFIED = 0; - - private Uri mDataUri; - - private byte[] mFingerprint; - private String mUserId; private Bitmap mQrCodeBitmapCache; + private UnifiedKeyInfo unifiedKeyInfo; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { @@ -102,34 +92,24 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements // just calls requestLayout when it is finished, this way the loader and // background task are disconnected from any layouting the ImageView may // undergo. Please note how these six lines are perfectly right-aligned. - mQrCode.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, - int oldRight, - int oldBottom) { - // bitmap scaling is expensive, avoid doing it if we already have the correct size! - int mCurrentWidth = 0, mCurrentHeight = 0; - if (mQrCodeBitmapCache != null) { - if (mCurrentWidth == mQrCode.getWidth() && mCurrentHeight == mQrCode.getHeight()) { - return; - } - mCurrentWidth = mQrCode.getWidth(); - mCurrentHeight = mQrCode.getHeight(); - // scale the image up to our actual size. we do this in code rather - // than let the ImageView do this because we don't require filtering. - Bitmap scaled = Bitmap.createScaledBitmap(mQrCodeBitmapCache, - mCurrentWidth, mCurrentHeight, false); - mQrCode.setImageBitmap(scaled); + mQrCode.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + // bitmap scaling is expensive, avoid doing it if we already have the correct size! + int mCurrentWidth = 0, mCurrentHeight = 0; + if (mQrCodeBitmapCache != null) { + if (mCurrentWidth == mQrCode.getWidth() && mCurrentHeight == mQrCode.getHeight()) { + return; } + mCurrentWidth = mQrCode.getWidth(); + mCurrentHeight = mQrCode.getHeight(); + // scale the image up to our actual size. we do this in code rather + // than let the ImageView do this because we don't require filtering. + Bitmap scaled = Bitmap.createScaledBitmap(mQrCodeBitmapCache, + mCurrentWidth, mCurrentHeight, false); + mQrCode.setImageBitmap(scaled); } }); mQrCodeLayout = view.findViewById(R.id.view_key_qr_code_layout); - mQrCodeLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showQrCodeDialog(); - } - }); + mQrCodeLayout.setOnClickListener(v -> showQrCodeDialog()); View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); @@ -139,106 +119,43 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements View vKeySshShareButton = view.findViewById(R.id.view_key_action_key_ssh_share); View vKeySshClipboardButton = view.findViewById(R.id.view_key_action_key_ssh_clipboard); View vKeyUploadButton = view.findViewById(R.id.view_key_action_upload); - vKeySafeSlingerButton.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), + vKeySafeSlingerButton.setColorFilter(FormattingUtils.getColorFromAttr(requireContext(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); - vFingerprintShareButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareFingerprint(false); - } - }); - vFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareFingerprint(true); - } - }); - vKeyShareButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareKey(false, false); - } - }); - vKeyClipboardButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareKey(true, false); - } - }); + vFingerprintShareButton.setOnClickListener(v -> shareFingerprint(false)); + vFingerprintClipboardButton.setOnClickListener(v -> shareFingerprint(true)); + vKeyShareButton.setOnClickListener(v -> shareKey(false, false)); + vKeyClipboardButton.setOnClickListener(v -> shareKey(true, false)); - vKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startSafeSlinger(mDataUri); - } - }); - vKeySshShareButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareKey(false, true); - } - }); - vKeySshClipboardButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareKey(true, true); - } - }); - vKeyUploadButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - uploadToKeyserver(); - } - }); + vKeySafeSlingerButton.setOnClickListener(v -> startSafeSlinger()); + vKeySshShareButton.setOnClickListener(v -> shareKey(false, true)); + vKeySshClipboardButton.setOnClickListener(v -> shareKey(true, true)); + vKeyUploadButton.setOnClickListener(v -> uploadToKeyserver()); return root; } - private void startSafeSlinger(Uri dataUri) { - long keyId = 0; - try { - keyId = KeyRepository.create(getContext()) - .getCachedPublicKeyRing(dataUri) - .extractOrGetMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); - } + private void startSafeSlinger() { Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, keyId); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivityForResult(safeSlingerIntent, 0); } - private boolean hasAuthenticationKey() { - KeyRepository keyRepository = KeyRepository.create(getContext()); - long masterKeyId = Constants.key.none; - long authSubKeyId = Constants.key.none; - try { - masterKeyId = keyRepository.getCachedPublicKeyRing(mDataUri).extractOrGetMasterKeyId(); - CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId); - authSubKeyId = cachedPublicKeyRing.getAuthenticationId(); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); - } - return authSubKeyId != Constants.key.none; - } - private String getShareKeyContent(boolean asSshKey) throws PgpKeyNotFoundException, KeyRepository.NotFoundException, IOException, PgpGeneralException, NoSuchAlgorithmException { - KeyRepository keyRepository = KeyRepository.create(getContext()); + KeyRepository keyRepository = KeyRepository.create(requireContext()); String content; - long masterKeyId = keyRepository.getCachedPublicKeyRing(mDataUri).extractOrGetMasterKeyId(); if (asSshKey) { - long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getAuthenticationId(); - CanonicalizedPublicKey publicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) + long authSubKeyId = keyRepository.getCachedPublicKeyRing(unifiedKeyInfo.master_key_id()).getAuthenticationId(); + CanonicalizedPublicKey publicKey = keyRepository.getCanonicalizedPublicKeyRing(unifiedKeyInfo.master_key_id()) .getPublicKey(authSubKeyId); SshPublicKey sshPublicKey = new SshPublicKey(publicKey); content = sshPublicKey.getEncodedKey(); } else { - content = keyRepository.getPublicKeyRingAsArmoredString(masterKeyId); + content = keyRepository.getPublicKeyRingAsArmoredString(unifiedKeyInfo.master_key_id()); } return content; @@ -246,10 +163,10 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private void shareKey(boolean toClipboard, boolean asSshKey) { Activity activity = getActivity(); - if (activity == null || mFingerprint == null) { + if (activity == null || unifiedKeyInfo == null) { return; } - if (asSshKey && !hasAuthenticationKey()) { + if (asSshKey && !unifiedKeyInfo.has_auth_key()) { Notify.create(activity, R.string.authentication_subkey_not_found, Style.ERROR).show(); return; } @@ -281,10 +198,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements try { TemporaryFileProvider shareFileProv = new TemporaryFileProvider(); - String filename = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); - OpenPgpUtils.UserId mainUserId = KeyRing.splitUserId(mUserId); - if (mainUserId.name != null) { - filename = mainUserId.name; + String filename; + if (unifiedKeyInfo.name() != null) { + filename = unifiedKeyInfo.name(); + } else { + filename = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); } Uri contentUri = TemporaryFileProvider.createFile(activity, filename + Constants.FILE_EXTENSION_ASC); @@ -316,12 +234,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private void shareFingerprint(boolean toClipboard) { Activity activity = getActivity(); - if (activity == null || mFingerprint == null) { + if (activity == null || unifiedKeyInfo == null) { return; } String content; - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); if (!toClipboard) { content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; } else { @@ -365,141 +283,63 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements opts = options.toBundle(); } - qrCodeIntent.setData(mDataUri); - ActivityCompat.startActivity(getActivity(), qrCodeIntent, opts); + qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + ActivityCompat.startActivity(requireActivity(), qrCodeIntent, opts); } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); - getActivity().finish(); + ViewKeyAdvViewModel viewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyAdvViewModel.class); + LiveData unifiedKeyInfoLiveData = viewModel.getUnifiedKeyInfoLiveData(requireContext()); + unifiedKeyInfoLiveData.observe(this, this::onLoadUnifiedKeyInfo); + + LiveData qrCodeLiveData = Transformations.switchMap(unifiedKeyInfoLiveData, + (unifiedKeyInfo) -> new GenericLiveData<>(getContext(), null, + () -> { + String fingerprintHex = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); + Uri uri = new Uri.Builder().scheme(Constants.FINGERPRINT_SCHEME).opaquePart(fingerprintHex).build(); + // render with minimal size + return QrCodeUtils.getQRCodeBitmap(uri, 0); + } + )); + qrCodeLiveData.observe(this, this::onLoadQrCode); + } + + public void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { return; } - loadData(dataUri); - } + this.unifiedKeyInfo = unifiedKeyInfo; - private void loadData(Uri dataUri) { - mDataUri = dataUri; + final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); + mFingerprintView.setText(KeyFormattingUtils.formatFingerprint(fingerprint)); - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - } - - static final String[] UNIFIED_PROJECTION = new String[]{ - KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.USER_ID - }; - - static final int INDEX_UNIFIED_FINGERPRINT = 1; - static final int INDEX_UNIFIED_USER_ID = 2; - - public Loader onCreateLoader(int id, Bundle args) { - setContentShown(false); - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); - } - - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Avoid NullPointerExceptions... - if (data == null || data.getCount() == 0) { - return; - } - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - if (data.moveToFirst()) { - - byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); - setFingerprint(fingerprintBlob); - - mUserId = data.getString(INDEX_UNIFIED_USER_ID); - - break; - } - } - - } setContentShown(true); } - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - mFingerprint = null; - mQrCodeBitmapCache = null; - } - - /** - * Load QR Code asynchronously and with a fade in animation - */ - private void setFingerprint(byte[] fingerprintBlob) { - mFingerprint = fingerprintBlob; - - final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); - mFingerprintView.setText(KeyFormattingUtils.formatFingerprint(fingerprint)); - + private void onLoadQrCode(Bitmap qrCode) { if (mQrCodeBitmapCache != null) { return; } - AsyncTask loadTask = - new AsyncTask() { - protected Bitmap doInBackground(Void... unused) { - Uri uri = new Uri.Builder() - .scheme(Constants.FINGERPRINT_SCHEME) - .opaquePart(fingerprint) - .build(); - // render with minimal size - return QrCodeUtils.getQRCodeBitmap(uri, 0); - } + mQrCodeBitmapCache = qrCode; + if (ViewKeyAdvShareFragment.this.isAdded()) { + mQrCode.requestLayout(); - protected void onPostExecute(Bitmap qrCode) { - // cache for later, and if we are attached request re-layout - mQrCodeBitmapCache = qrCode; - - if (ViewKeyAdvShareFragment.this.isAdded()) { - mQrCode.requestLayout(); - - // simple fade-in animation - AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); - anim.setDuration(200); - mQrCode.startAnimation(anim); - } - } - }; - - loadTask.execute(); + // simple fade-in animation + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + mQrCode.startAnimation(anim); + } } private void uploadToKeyserver() { - long keyId; - try { - keyId = KeyRepository.create(getContext()) - .getCachedPublicKeyRing(mDataUri) - .extractOrGetMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); - 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}); + uploadIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{ unifiedKeyInfo.master_key_id() }); startActivityForResult(uploadIntent, 0); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index 1d5eff9fe..3ffb268c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -20,38 +20,31 @@ package org.sufficientlysecure.keychain.ui; import java.util.List; -import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.ListView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; +import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -59,16 +52,9 @@ import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; -import timber.log.Timber; -public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks { - - public static final String ARG_DATA_URI = "data_uri"; - - private static final int LOADER_ID_UNIFIED = 0; - +public class ViewKeyAdvSubkeysFragment extends LoaderFragment { private ListView mSubkeysList; private ListView mSubkeysAddedList; private View mSubkeysAddedLayout; @@ -79,11 +65,8 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements private CryptoOperationHelper mEditKeyHelper; - private Uri mDataUri; - - private long mMasterKeyId; - private byte[] mFingerprint; private SaveKeyringParcel.Builder mEditModeSkpBuilder; + private UnifiedKeyInfo unifiedKeyInfo; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { @@ -94,12 +77,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeysAddedList = view.findViewById(R.id.view_key_subkeys_added); mSubkeysAddedLayout = view.findViewById(R.id.view_key_subkeys_add_layout); - mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - editSubkey(position); - } - }); + mSubkeysList.setOnItemClickListener((parent, view1, position, id) -> editSubkey(position)); View footer = new View(getActivity()); int spacing = (int) android.util.TypedValue.applyDimension( @@ -113,12 +91,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeysAddedList.addFooterView(footer, null, false); mSubkeyAddFabLayout = view.findViewById(R.id.view_key_subkey_fab_layout); - view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addSubkey(); - } - }); + view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(v -> addSubkey()); setHasOptionsMenu(true); @@ -129,16 +102,32 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); - getActivity().finish(); + // Create an empty adapter we will use to display the loaded data. + mSubkeysAdapter = new SubkeysAdapter(requireContext()); + mSubkeysList.setAdapter(mSubkeysAdapter); + + ViewKeyAdvViewModel viewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyAdvViewModel.class); + viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadFinished); + viewModel.getSubkeyLiveData(requireContext()).observe(this, this::onLoadSubKeys); + + setContentShown(false); + } + + public void onLoadFinished(UnifiedKeyInfo unifiedKeyInfo) { + // Avoid NullPointerExceptions, if we get an empty result set. + if (unifiedKeyInfo == null) { return; } - loadData(dataUri); + this.unifiedKeyInfo = unifiedKeyInfo; } + private void onLoadSubKeys(List subKeys) { + mSubkeysAdapter.setData(subKeys); + setContentShown(true); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mEditKeyHelper != null) { @@ -148,82 +137,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements super.onActivityResult(requestCode, resultCode, data); } - private void loadData(Uri dataUri) { - mDataUri = dataUri; - - // Create an empty adapter we will use to display the loaded data. - mSubkeysAdapter = new SubkeysAdapter(requireContext()); - mSubkeysList.setAdapter(mSubkeysAdapter); - - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - - setContentShown(false); - } - - // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - }; - - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_HAS_ANY_SECRET = 2; - static final int INDEX_FINGERPRINT = 3; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, - PROJECTION, null, null, null); - } - - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Avoid NullPointerExceptions, if we get an empty result set. - if (data.getCount() == 0) { - return; - } - - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - data.moveToFirst(); - - mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - mFingerprint = data.getBlob(INDEX_FINGERPRINT); - - KeyRepository keyRepository = KeyRepository.create(requireContext()); - LiveData> subKeyLiveData = new GenericLiveData<>(requireContext(), null, - () -> keyRepository.getSubKeysByMasterKeyId(mMasterKeyId)); - subKeyLiveData.observe(this, this::onLoadSubKeys); - break; - } - } - - } - - private void onLoadSubKeys(List subKeys) { - mSubkeysAdapter.setData(subKeys); - setContentShown(true); - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -244,7 +157,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mEditModeSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(mMasterKeyId, mFingerprint); + mEditModeSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(unifiedKeyInfo.master_key_id(), unifiedKeyInfo.fingerprint()); mSubkeysAddedAdapter = new SubkeysAddedAdapter( getActivity(), mEditModeSkpBuilder.getMutableAddSubKeys(), false); @@ -292,15 +205,8 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements AddSubkeyDialogFragment addSubkeyDialogFragment = AddSubkeyDialogFragment.newInstance(willBeMasterKey); addSubkeyDialogFragment - .setOnAlgorithmSelectedListener( - new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { - @Override - public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { - mSubkeysAddedAdapter.add(newSubkey); - } - } - ); - addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog"); + .setOnAlgorithmSelectedListener(newSubkey -> mSubkeysAddedAdapter.add(newSubkey)); + addSubkeyDialogFragment.show(requireFragmentManager(), "addSubkeyDialog"); } private void editSubkey(final int position) { @@ -343,13 +249,11 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - EditSubkeyDialogFragment dialogFragment = - EditSubkeyDialogFragment.newInstance(messenger); + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(() -> { + EditSubkeyDialogFragment dialogFragment = + EditSubkeyDialogFragment.newInstance(messenger); - dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog"); - } + dialogFragment.show(requireFragmentManager(), "editSubkeyDialog"); }); } @@ -377,13 +281,11 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - EditSubkeyExpiryDialogFragment dialogFragment = - EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(() -> { + EditSubkeyExpiryDialogFragment dialogFragment = + EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); - dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog"); - } + dialogFragment.show(requireFragmentManager(), "editSubkeyExpiryDialog"); }); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index 9ce7058c8..98b6fe07a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -20,39 +20,30 @@ package org.sufficientlysecure.keychain.ui; import java.util.List; -import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.ListView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -61,16 +52,9 @@ import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; -import timber.log.Timber; -public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks { - - public static final String ARG_DATA_URI = "uri"; - - private static final int LOADER_ID_UNIFIED = 0; - +public class ViewKeyAdvUserIdsFragment extends LoaderFragment { private ListView mUserIds; private ListView mUserIdsAddedList; private View mUserIdsAddedLayout; @@ -81,12 +65,8 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements private CryptoOperationHelper mEditKeyHelper; - private Uri mDataUri; - - private long mMasterKeyId; - private byte[] mFingerprint; - private boolean mHasSecret; private SaveKeyringParcel.Builder mSkpBuilder; + private UnifiedKeyInfo unifiedKeyInfo; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { @@ -97,12 +77,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements mUserIdsAddedList = view.findViewById(R.id.view_key_user_ids_added); mUserIdsAddedLayout = view.findViewById(R.id.view_key_user_ids_add_layout); - mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - showOrEditUserIdInfo(position); - } - }); + mUserIds.setOnItemClickListener((parent, view1, position, id) -> showOrEditUserIdInfo(position)); View footer = new View(getActivity()); int spacing = (int) android.util.TypedValue.applyDimension( @@ -116,12 +91,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements mUserIdsAddedList.addFooterView(footer, null, false); mUserIdAddFabLayout = view.findViewById(R.id.view_key_subkey_fab_layout); - view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addUserId(); - } - }); + view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(v -> addUserId()); setHasOptionsMenu(true); @@ -175,12 +145,10 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - EditUserIdDialogFragment dialogFragment = - EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); - dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); - } + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(() -> { + EditUserIdDialogFragment dialogFragment = + EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); + dialogFragment.show(requireFragmentManager(), "editUserIdDialog"); }); } @@ -189,13 +157,11 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final boolean isVerified = mUserIdsAdapter.getIsVerified(position) == Certs.VERIFIED_SECRET; - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - UserIdInfoDialogFragment dialogFragment = - UserIdInfoDialogFragment.newInstance(isRevoked, isVerified); + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(() -> { + UserIdInfoDialogFragment dialogFragment = + UserIdInfoDialogFragment.newInstance(isRevoked, isVerified); - dialogFragment.show(getActivity().getSupportFragmentManager(), "userIdInfoDialog"); - } + dialogFragment.show(requireFragmentManager(), "userIdInfoDialog"); }); } @@ -219,21 +185,33 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements // pre-fill out primary name AddUserIdDialogFragment addUserIdDialog = AddUserIdDialogFragment.newInstance(messenger, ""); - addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); + addUserIdDialog.show(requireFragmentManager(), "addUserIdDialog"); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); - getActivity().finish(); + mUserIdsAdapter = new UserIdsAdapter(getActivity(), false); + mUserIds.setAdapter(mUserIdsAdapter); + + ViewKeyAdvViewModel viewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyAdvViewModel.class); + viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); + viewModel.getUserIdLiveData(requireContext()).observe(this, this::onLoadUserIds); + + setContentShown(false); + } + + public void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { return; } + this.unifiedKeyInfo = unifiedKeyInfo; + } - loadData(dataUri); + private void onLoadUserIds(List userIds) { + mUserIdsAdapter.setData(userIds); + setContentShown(true); } @Override @@ -245,88 +223,6 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements super.onActivityResult(requestCode, resultCode, data); } - private void loadData(Uri dataUri) { - mDataUri = dataUri; - - Timber.i("dataUri: " + mDataUri); - - mUserIdsAdapter = new UserIdsAdapter(getActivity(), false); - mUserIds.setAdapter(mUserIdsAdapter); - - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - - KeyRepository keyRepository = KeyRepository.create(getContext()); - try { - Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); - CachedPublicKeyRing keyRing = keyRepository.getCachedPublicKeyRing(uri); - long masterKeyId = keyRing.getMasterKeyId(); - - LiveData> userIdLiveData = - new GenericLiveData<>(getContext(), null, () -> keyRepository.getUserIds(masterKeyId)); - userIdLiveData.observe(this, this::onUserIdsLoaded); - } catch (PgpKeyNotFoundException e) { - e.printStackTrace(); - } - } - - private void onUserIdsLoaded(List userIds) { - mUserIdsAdapter.setData(userIds); - setContentShown(true); - } - - // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - }; - - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_HAS_ANY_SECRET = 2; - static final int INDEX_FINGERPRINT = 3; - - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, - PROJECTION, null, null, null); - } - - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Avoid NullPointerExceptions, if we get an empty result set. - if (data.getCount() == 0) { - return; - } - - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - data.moveToFirst(); - - mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - mFingerprint = data.getBlob(INDEX_FINGERPRINT); - break; - } - } - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -346,8 +242,8 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements activity.startActionMode(new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - - mSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(mMasterKeyId, mFingerprint); + mSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel( + unifiedKeyInfo.master_key_id(), unifiedKeyInfo.fingerprint()); mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSkpBuilder.getMutableAddUserIds(), false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java index f483d09c9..3040a710e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java @@ -17,6 +17,11 @@ package org.sufficientlysecure.keychain.ui; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + import android.content.Intent; import android.database.Cursor; import android.graphics.Typeface; @@ -43,29 +48,23 @@ import com.textuality.keybase.lib.KeybaseException; import com.textuality.keybase.lib.KeybaseQuery; import com.textuality.keybase.lib.Proof; import com.textuality.keybase.lib.User; - import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; 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.util.ArrayList; -import java.util.Hashtable; -import java.util.List; public class ViewKeyKeybaseFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { - public static final String ARG_DATA_URI = "uri"; + public static final String ARG_MASTER_KEY_ID = "master_key_id"; private TextView mReportHeader; private TableLayout mProofListing; @@ -76,7 +75,7 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements private static final int LOADER_ID_DATABASE = 1; // for retrieving the key we’re working on - private Uri mDataUri; + private long masterKeyId; private Proof mProof; @@ -89,10 +88,10 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements /** * Creates new instance of this fragment */ - public static ViewKeyKeybaseFragment newInstance(Uri dataUri) { + public static ViewKeyKeybaseFragment newInstance(long masterKeyId) { ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); + args.putLong(ARG_MASTER_KEY_ID, masterKeyId); frag.setArguments(args); @@ -121,13 +120,10 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); - getActivity().finish(); - return; + masterKeyId = getArguments().getLong(ARG_MASTER_KEY_ID); + if (masterKeyId == 0L) { + throw new IllegalArgumentException(); } - mDataUri = dataUri; // retrieve the key from the database getLoaderManager().initLoader(LOADER_ID_DATABASE, null, this); @@ -148,7 +144,7 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements switch (id) { case LOADER_ID_DATABASE: { - Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); + Uri baseUri = KeyRings.buildUnifiedKeyRingUri(masterKeyId); return new CursorLoader(getActivity(), baseUri, TRUST_PROJECTION, null, null, null); } // decided to just use an AsyncTask for keybase, but maybe later diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 633300e70..31e595161 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -18,6 +18,11 @@ package org.sufficientlysecure.keychain.ui.adapter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + import android.content.Intent; import android.databinding.DataBindingUtil; import android.support.v4.app.FragmentActivity; @@ -26,6 +31,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; @@ -40,7 +46,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; @@ -49,11 +54,6 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import timber.log.Timber; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - public class ImportKeysAdapter extends RecyclerView.Adapter implements ImportKeysResultListener { @@ -182,8 +182,7 @@ public class ImportKeysAdapter extends RecyclerView.Adapter importOpHelper; private CryptoOperationHelper editOpHelper; @@ -168,6 +168,12 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private long masterKeyId; private byte[] fingerprint; + public static Intent getViewKeyActivityIntent(@NonNull Context context, long masterKeyId) { + Intent viewIntent = new Intent(context, ViewKeyActivity.class); + viewIntent.putExtra(ViewKeyActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + return viewIntent; + } + @SuppressLint("InflateParams") @Override protected void onCreate(Bundle savedInstanceState) { @@ -244,30 +250,29 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements }); refreshView = getLayoutInflater().inflate(R.layout.indeterminate_progress, null); - dataUri = getIntent().getData(); - if (dataUri == null) { - Timber.e("Data missing. Should be uri of key!"); - finish(); - return; - } - if (dataUri.getHost().equals(ContactsContract.AUTHORITY)) { - dataUri = new ContactHelper(this).dataUriFromContactUri(dataUri); - if (dataUri == null) { + Intent intent = getIntent(); + Uri dataUri = intent.getData(); + if (intent.hasExtra(EXTRA_MASTER_KEY_ID)) { + masterKeyId = intent.getLongExtra(EXTRA_MASTER_KEY_ID, 0L); + } else if (dataUri != null && dataUri.getHost().equals(ContactsContract.AUTHORITY)) { + Long contactMasterKeyId = new ContactHelper(this).masterKeyIdFromContactsDataUri(dataUri); + if (contactMasterKeyId == null) { Timber.e("Contact Data missing. Should be uri of key!"); Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); finish(); return; } + masterKeyId = contactMasterKeyId; + } else { + throw new IllegalArgumentException("Missing required extra master_key_id or contact uri"); } - Timber.i("dataUri: " + dataUri); - - actionEncryptFile.setOnClickListener(v -> encrypt(dataUri, false)); - actionEncryptText.setOnClickListener(v -> encrypt(dataUri, true)); + actionEncryptFile.setOnClickListener(v -> encrypt(false)); + actionEncryptText.setOnClickListener(v -> encrypt(true)); floatingActionButton.setOnClickListener(v -> { if (isSecret) { - startSafeSlinger(dataUri); + startSafeSlinger(); } else { scanQrCode(); } @@ -279,8 +284,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) { - OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT); + if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) { + OperationResult result = intent.getParcelableExtra(EXTRA_DISPLAY_RESULT); result.createNotify(this).show(); } @@ -291,7 +296,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { FragmentManager manager = getSupportFragmentManager(); - final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(dataUri); + final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(masterKeyId); manager.beginTransaction() .replace(R.id.view_key_keybase_fragment, keybaseFrag) .commit(); @@ -341,7 +346,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } case R.id.menu_key_view_advanced: { Intent advancedIntent = new Intent(this, ViewKeyAdvActivity.class); - advancedIntent.setData(dataUri); + advancedIntent.putExtra(ViewKeyAdvActivity.EXTRA_MASTER_KEY_ID, masterKeyId); startActivity(advancedIntent); return true; } @@ -350,7 +355,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements return true; } case R.id.menu_key_view_certify_fingerprint: { - certifyFingerprint(dataUri); + certifyFingerprint(); return true; } } @@ -440,9 +445,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT); } - private void certifyFingerprint(Uri dataUri) { + private void certifyFingerprint() { Intent intent = new Intent(this, CertifyFingerprintActivity.class); - intent.setData(dataUri); + intent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); startActivityForResult(intent, REQUEST_CERTIFY); } @@ -466,7 +471,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements opts = options.toBundle(); } - qrCodeIntent.setData(dataUri); + qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); ActivityCompat.startActivity(this, qrCodeIntent, opts); } @@ -615,45 +620,30 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements }); } - private void encrypt(Uri dataUri, boolean text) { + private void encrypt(boolean text) { // If there is no encryption key, don't bother. if (!hasEncrypt) { Notify.create(this, R.string.error_no_encrypt_subkey, Notify.Style.ERROR).show(); return; } - try { - long keyId = KeyRepository.create(this) - .getCachedPublicKeyRing(dataUri) - .extractOrGetMasterKeyId(); - long[] encryptionKeyIds = new long[]{keyId}; - Intent intent; - if (text) { - intent = new Intent(this, EncryptTextActivity.class); - intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT); - intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); - } else { - intent = new Intent(this, EncryptFilesActivity.class); - intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); - intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); - } - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(intent, 0); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); + long[] encryptionKeyIds = new long[] { masterKeyId }; + Intent intent; + if (text) { + intent = new Intent(this, EncryptTextActivity.class); + intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT); + intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); + } else { + intent = new Intent(this, EncryptFilesActivity.class); + intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); + intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); } + // used instead of startActivity set actionbar based on callingPackage + startActivityForResult(intent, 0); } - private void startSafeSlinger(Uri dataUri) { - long keyId = 0; - try { - keyId = KeyRepository.create(this) - .getCachedPublicKeyRing(dataUri) - .extractOrGetMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); - } + private void startSafeSlinger() { Intent safeSlingerIntent = new Intent(this, SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, keyId); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); startActivityForResult(safeSlingerIntent, 0); } @@ -727,7 +717,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri); + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId); return new CursorLoader(this, baseUri, PROJECTION, null, null, null); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java index 624a51055..bc6c79dae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui.linked; + import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -61,19 +62,18 @@ import android.widget.TextView; import android.widget.ViewAnimator; import javax.net.ssl.HttpsURLConnection; +import org.bouncycastle.util.encoders.Hex; import org.json.JSONException; import org.json.JSONObject; -import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.resources.GithubResource; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; -import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.widget.StatusIndicator; @@ -433,8 +433,7 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment= Build.VERSION_CODES.LOLLIPOP) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java index 7371e486d..bcbac6e14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/ManageSecurityTokenFragment.java @@ -183,11 +183,7 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur public void finishAndShowKey(long masterKeyId) { Activity activity = getActivity(); - Intent viewKeyIntent = new Intent(activity, ViewKeyActivity.class); - // use the imported masterKeyId, not the one from the token, because - // that one might* just have been a subkey of the imported key - viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - + Intent viewKeyIntent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), masterKeyId); if (activity instanceof CreateKeyActivity) { ((CreateKeyActivity) activity).finishWithFirstTimeHandling(viewKeyIntent); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 985880b75..5a57412d1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -322,7 +322,7 @@ public class ContactHelper { return new ArrayList<>(names); } - public Uri dataUriFromContactUri(Uri contactUri) { + public Long masterKeyIdFromContactsDataUri(Uri contactUri) { if (!isContactsPermissionGranted()) { return null; } @@ -332,7 +332,7 @@ public class ContactHelper { if (contactMasterKey != null) { try { if (contactMasterKey.moveToNext()) { - return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0)); + return contactMasterKey.getLong(0); } } finally { contactMasterKey.close(); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index c147c0d1f..6235a5b73 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -24,9 +24,10 @@ CREATE TABLE IF NOT EXISTS keys ( ); selectAllUnifiedKeyInfo: -SELECT keys.master_key_id, MIN(user_packets.rank), user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, certs.verified, +SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, certs.verified, (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, - (EXISTS (SELECT * FROM keys AS k WHERE k.has_secret != 0 AND k.master_key_id = keys.master_key_id )) AS has_any_secret_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list FROM keys @@ -37,6 +38,20 @@ SELECT keys.master_key_id, MIN(user_packets.rank), user_packets.name, user_packe GROUP BY keys.master_key_id ORDER BY has_secret DESC, user_packets.name COLLATE NOCASE ASC; +selectUnifiedKeyInfoByMasterKeyId: +SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, certs.verified, + (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, + GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, + GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list + FROM keys + INNER JOIN user_packets ON ( keys.master_key_id = user_packets.master_key_id AND user_packets.type IS NULL ) + LEFT JOIN certs ON ( keys.master_key_id = certs.master_key_id AND certs.verified = 1 ) + LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id ) + WHERE keys.rank = 0 AND keys.master_key_id = ? + GROUP BY keys.master_key_id; + selectSubkeysByMasterKeyId: SELECT * FROM keys From 5cfe0d140f5f6c579212b328a625c6d7e73d1bbe Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 01:14:07 +0200 Subject: [PATCH 031/124] use LiveData to load data in ViewKeyActivity --- .../keychain/model/SubKey.java | 4 + .../keychain/ui/keyview/ViewKeyActivity.java | 483 ++++++++---------- .../keychain/ui/keyview/ViewKeyFragment.java | 83 +-- .../presenter/IdentitiesPresenter.java | 1 - .../keyview/presenter/KeyHealthPresenter.java | 1 - .../ui/keyview/presenter/ViewKeyMvpView.java | 1 - .../org/sufficientlysecure/keychain/Keys.sq | 2 + 7 files changed, 235 insertions(+), 340 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 5505c39c4..0f5463810 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -58,5 +58,9 @@ public abstract class SubKey implements KeysModel { public boolean has_auth_key() { return has_auth_key_int() != 0; } + + public boolean has_encrypt_key() { + return has_encrypt_key_int() != 0; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 52463dc0f..052c05a48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -28,9 +28,11 @@ import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.net.Uri; @@ -49,9 +51,6 @@ import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.view.Menu; import android.view.MenuItem; @@ -71,14 +70,14 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; @@ -113,7 +112,6 @@ import timber.log.Timber; public class ViewKeyActivity extends BaseSecurityTokenActivity implements - LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { @Retention(RetentionPolicy.SOURCE) @IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE}) @@ -151,23 +149,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private byte[] qrCodeLoaded; - private static final int LOADER_ID_UNIFIED = 0; - - private boolean isSecret = false; - private boolean hasEncrypt = false; - private boolean isVerified = false; - private boolean isRevoked = false; - private boolean isSecure = true; - private boolean isExpired = false; + private UnifiedKeyInfo unifiedKeyInfo; private MenuItem refreshItem; private boolean isRefreshing; private Animation rotate, rotateSpin; private View refreshView; - private long masterKeyId; - private byte[] fingerprint; - public static Intent getViewKeyActivityIntent(@NonNull Context context, long masterKeyId) { Intent viewIntent = new Intent(context, ViewKeyActivity.class); viewIntent.putExtra(ViewKeyActivity.EXTRA_MASTER_KEY_ID, masterKeyId); @@ -250,6 +238,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements }); refreshView = getLayoutInflater().inflate(R.layout.indeterminate_progress, null); + long masterKeyId; Intent intent = getIntent(); Uri dataUri = intent.getData(); if (intent.hasExtra(EXTRA_MASTER_KEY_ID)) { @@ -271,7 +260,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements actionEncryptText.setOnClickListener(v -> encrypt(true)); floatingActionButton.setOnClickListener(v -> { - if (isSecret) { + if (unifiedKeyInfo.has_any_secret()) { startSafeSlinger(); } else { scanQrCode(); @@ -280,9 +269,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements qrCodeLayout.setOnClickListener(v -> showQrCodeDialog()); - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + ViewKeyViewModel viewModel = ViewModelProviders.of(this).get(ViewKeyViewModel.class); + viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L)); if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) { OperationResult result = intent.getParcelableExtra(EXTRA_DISPLAY_RESULT); @@ -294,8 +282,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements return; } + FragmentManager manager = getSupportFragmentManager(); + + ViewKeyFragment frag = ViewKeyFragment.newInstance(); + manager.beginTransaction().replace(R.id.view_key_fragment, frag, "view_key_fragment").commit(); + if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { - FragmentManager manager = getSupportFragmentManager(); + manager = getSupportFragmentManager(); final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(masterKeyId); manager.beginTransaction() .replace(R.id.view_key_keybase_fragment, keybaseFrag) @@ -303,6 +296,30 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } } + public static class ViewKeyViewModel extends ViewModel { + private Long masterKeyId; + private LiveData unifiedKeyInfoLiveData; + + void setMasterKeyId(long masterKeyId) { + if (this.masterKeyId != null) { + throw new IllegalStateException("cannot change masterKeyId once set!"); + } + this.masterKeyId = masterKeyId; + } + + LiveData getUnifiedKeyInfoLiveData(Context context) { + if (masterKeyId == null) { + throw new IllegalStateException("masterKeyId must be set to retrieve this!"); + } + if (unifiedKeyInfoLiveData == null) { + KeyRepository keyRepository = KeyRepository.create(context); + unifiedKeyInfoLiveData = new GenericLiveData<>(context, null, + () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); + } + return unifiedKeyInfoLiveData; + } + } + @Override protected void initLayout() { setContentView(R.layout.view_key_activity); @@ -346,7 +363,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } case R.id.menu_key_view_advanced: { Intent advancedIntent = new Intent(this, ViewKeyAdvActivity.class); - advancedIntent.putExtra(ViewKeyAdvActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + advancedIntent.putExtra(ViewKeyAdvActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivity(advancedIntent); return true; } @@ -365,13 +382,15 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem backupKey = menu.findItem(R.id.menu_key_view_backup); - backupKey.setVisible(isSecret); - menu.findItem(R.id.menu_key_view_skt).setVisible(isSecret); + backupKey.setVisible(unifiedKeyInfo.has_any_secret()); + menu.findItem(R.id.menu_key_view_skt).setVisible(unifiedKeyInfo.has_any_secret()); MenuItem changePassword = menu.findItem(R.id.menu_key_change_password); - changePassword.setVisible(isSecret); + changePassword.setVisible(unifiedKeyInfo.has_any_secret()); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); - certifyFingerprint.setVisible(!isSecret && !isVerified && !isExpired && !isRevoked); + certifyFingerprint.setVisible( + !unifiedKeyInfo.has_any_secret() && !unifiedKeyInfo.is_verified() && !unifiedKeyInfo.is_expired() && + !unifiedKeyInfo.is_revoked()); return true; } @@ -387,6 +406,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements @Override public void onCryptoOperationSuccess(EditKeyResult result) { displayResult(result); + long masterKeyId = unifiedKeyInfo.master_key_id(); PassphraseCacheService.clearCachedPassphrase(getApplicationContext(), masterKeyId, masterKeyId); } @@ -417,7 +437,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // use new passphrase! changeUnlockParcel = ChangeUnlockParcel.createChangeUnlockParcel( - masterKeyId, fingerprint, + unifiedKeyInfo.master_key_id(), unifiedKeyInfo.fingerprint(), data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); @@ -447,14 +467,14 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void certifyFingerprint() { Intent intent = new Intent(this, CertifyFingerprintActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); + intent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); startActivityForResult(intent, REQUEST_CERTIFY); } private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); - intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { masterKeyId }); + intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { unifiedKeyInfo.master_key_id() }); startActivityForResult(intent, REQUEST_CERTIFY); } @@ -471,7 +491,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements opts = options.toBundle(); } - qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); + qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); ActivityCompat.startActivity(this, qrCodeIntent, opts); } @@ -479,6 +499,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements if (keyHasPassphrase()) { Intent intent = new Intent(this, PassphraseDialogActivity.class); + long masterKeyId = unifiedKeyInfo.master_key_id(); RequiredInputParcel requiredInput = RequiredInputParcel.createRequiredDecryptPassphrase(masterKeyId, masterKeyId); requiredInput.mSkipCaching = true; @@ -491,6 +512,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private boolean keyHasPassphrase() { try { + long masterKeyId = unifiedKeyInfo.master_key_id(); SecretKeyType secretKeyType = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretKeyType(masterKeyId); switch (secretKeyType) { @@ -510,7 +532,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void startBackupActivity() { Intent intent = new Intent(this, BackupActivity.class); - intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[]{ masterKeyId }); + intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { unifiedKeyInfo.master_key_id() }); intent.putExtra(BackupActivity.EXTRA_SECRET, true); startActivity(intent); } @@ -519,9 +541,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class); deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, - new long[]{ masterKeyId }); - deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, isSecret); - if (isSecret) { + new long[]{ unifiedKeyInfo.master_key_id() }); + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, unifiedKeyInfo.has_any_secret()); + if (unifiedKeyInfo.has_any_secret()) { // for upload in case key is secret deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, Preferences.getPreferences(this).getPreferredKeyserver()); @@ -559,7 +581,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show(); return; } - if (Arrays.equals(this.fingerprint, fingerprint)) { + if (Arrays.equals(unifiedKeyInfo.fingerprint(), fingerprint)) { certifyImmediate(); } else { Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show(); @@ -598,35 +620,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements finish(); } - public void showMainFragment() { - new Handler().post(() -> { - FragmentManager manager = getSupportFragmentManager(); - - // unless we must refresh - ViewKeyFragment frag = (ViewKeyFragment) manager.findFragmentByTag("view_key_fragment"); - // if everything is valid, just drop it - if (frag != null && frag.isValidForData(isSecret)) { - return; - } - - // if the main fragment doesn't exist, or is not of the correct type, (re)create it - frag = ViewKeyFragment.newInstance(masterKeyId, isSecret); - // get rid of possible backstack, this fragment is always at the bottom - manager.popBackStack("security_token", FragmentManager.POP_BACK_STACK_INCLUSIVE); - manager.beginTransaction() - .replace(R.id.view_key_fragment, frag, "view_key_fragment") - // if this gets lost, it doesn't really matter since the loader will reinstate it onResume - .commitAllowingStateLoss(); - }); - } - private void encrypt(boolean text) { // If there is no encryption key, don't bother. - if (!hasEncrypt) { + if (!unifiedKeyInfo.has_encrypt_key()) { Notify.create(this, R.string.error_no_encrypt_subkey, Notify.Style.ERROR).show(); return; } - long[] encryptionKeyIds = new long[] { masterKeyId }; + long[] encryptionKeyIds = new long[] { unifiedKeyInfo.master_key_id() }; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); @@ -643,7 +643,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void startSafeSlinger() { Intent safeSlingerIntent = new Intent(this, SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivityForResult(safeSlingerIntent, 0); } @@ -682,50 +682,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements loadTask.execute(); } - - // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.IS_SECURE, - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.HAS_ENCRYPT, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT - }; - - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_USER_ID = 2; - static final int INDEX_IS_REVOKED = 3; - static final int INDEX_IS_EXPIRED = 4; - static final int INDEX_IS_SECURE = 5; - static final int INDEX_VERIFIED = 6; - static final int INDEX_HAS_ANY_SECRET = 7; - static final int INDEX_FINGERPRINT = 8; - static final int INDEX_HAS_ENCRYPT = 9; - static final int INDEX_NAME = 10; - static final int INDEX_EMAIL = 11; - static final int INDEX_COMMENT = 12; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId); - return new CursorLoader(this, baseUri, PROJECTION, null, null, null); - } - - default: - return null; - } - } - int mPreviousColor = 0; /** @@ -747,127 +703,104 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements return (0xff << 24) | (r << 16) | (g << 8) | b; } - @Override - public void onLoadFinished(Loader loader, Cursor data) { - /* TODO better error handling? May cause problems when a key is deleted, - * because the notification triggers faster than the activity closes. - */ + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { + return; + } - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - // Avoid NullPointerExceptions... - if (data.getCount() == 0) { - return; - } + this.unifiedKeyInfo = unifiedKeyInfo; - if (data.moveToFirst()) { - // get name, email, and comment from USER_ID + String name = unifiedKeyInfo.name(); + collapsingToolbarLayout.setTitle(name != null ? name : getString(R.string.user_id_no_name)); - String name = data.getString(INDEX_NAME); + // if the refresh animation isn't playing + if (!rotate.hasStarted() && !rotateSpin.hasStarted()) { + // re-create options menu based on isSecret, isVerified + supportInvalidateOptionsMenu(); + // this is done at the end of the animation otherwise + } - collapsingToolbarLayout.setTitle(name != null ? name : getString(R.string.user_id_no_name)); - - masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - fingerprint = data.getBlob(INDEX_FINGERPRINT); - isSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - hasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; - isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; - isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; - isSecure = data.getInt(INDEX_IS_SECURE) == 1; - isVerified = data.getInt(INDEX_VERIFIED) > 0; - - // queue showing of the main fragment - showMainFragment(); - - // if the refresh animation isn't playing - if (!rotate.hasStarted() && !rotateSpin.hasStarted()) { - // re-create options menu based on isSecret, isVerified - supportInvalidateOptionsMenu(); - // this is done at the end of the animation otherwise + AsyncTask photoTask = + new AsyncTask() { + protected Bitmap doInBackground(Long... mMasterKeyId) { + return new ContactHelper(ViewKeyActivity.this) + .loadPhotoByMasterKeyId(mMasterKeyId[0], true); } - AsyncTask photoTask = - new AsyncTask() { - protected Bitmap doInBackground(Long... mMasterKeyId) { - return new ContactHelper(ViewKeyActivity.this) - .loadPhotoByMasterKeyId(mMasterKeyId[0], true); - } - - protected void onPostExecute(Bitmap photo) { - if (photo == null) { - return; - } - - photoView.setImageBitmap(photo); - photoView.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP); - photoLayout.setVisibility(View.VISIBLE); - } - }; - - boolean showStatusText = isSecure && !isExpired && !isRevoked; - if (showStatusText) { - statusText.setVisibility(View.VISIBLE); - - if (isSecret) { - statusText.setText(R.string.view_key_my_key); - } else if (isVerified) { - statusText.setText(R.string.view_key_verified); - } else { - statusText.setText(R.string.view_key_unverified); + protected void onPostExecute(Bitmap photo) { + if (photo == null) { + return; } - } else { - statusText.setVisibility(View.GONE); + + photoView.setImageBitmap(photo); + photoView.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), + PorterDuff.Mode.SRC_ATOP); + photoLayout.setVisibility(View.VISIBLE); } + }; - // Note: order is important - int color; - if (isRevoked) { - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.REVOKED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_red); + boolean showStatusText = unifiedKeyInfo.is_secure() && !unifiedKeyInfo.is_expired() && !unifiedKeyInfo.is_revoked(); + if (showStatusText) { + statusText.setVisibility(View.VISIBLE); - actionEncryptFile.setVisibility(View.INVISIBLE); - actionEncryptText.setVisibility(View.INVISIBLE); - hideFab(); - qrCodeLayout.setVisibility(View.GONE); - } else if (!isSecure) { - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.INSECURE, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_red); + if (unifiedKeyInfo.has_any_secret()) { + statusText.setText(R.string.view_key_my_key); + } else if (unifiedKeyInfo.is_verified()) { + statusText.setText(R.string.view_key_verified); + } else { + statusText.setText(R.string.view_key_unverified); + } + } else { + statusText.setVisibility(View.GONE); + } - actionEncryptFile.setVisibility(View.INVISIBLE); - actionEncryptText.setVisibility(View.INVISIBLE); - hideFab(); - qrCodeLayout.setVisibility(View.GONE); - } else if (isExpired) { - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.EXPIRED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_red); + // Note: order is important + int color; + if (unifiedKeyInfo.is_revoked()) { + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.REVOKED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_red); - actionEncryptFile.setVisibility(View.INVISIBLE); - actionEncryptText.setVisibility(View.INVISIBLE); - hideFab(); - qrCodeLayout.setVisibility(View.GONE); - } else if (isSecret) { - statusImage.setVisibility(View.GONE); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_green); - // reload qr code only if the fingerprint changed - if (!Arrays.equals(fingerprint, qrCodeLoaded)) { - loadQrCode(fingerprint); - } - photoTask.execute(masterKeyId); - qrCodeLayout.setVisibility(View.VISIBLE); + actionEncryptFile.setVisibility(View.INVISIBLE); + actionEncryptText.setVisibility(View.INVISIBLE); + hideFab(); + qrCodeLayout.setVisibility(View.GONE); + } else if (!unifiedKeyInfo.is_secure()) { + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.INSECURE, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_red); - // and place leftOf qr code + actionEncryptFile.setVisibility(View.INVISIBLE); + actionEncryptText.setVisibility(View.INVISIBLE); + hideFab(); + qrCodeLayout.setVisibility(View.GONE); + } else if (unifiedKeyInfo.is_expired()) { + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.EXPIRED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_red); + + actionEncryptFile.setVisibility(View.INVISIBLE); + actionEncryptText.setVisibility(View.INVISIBLE); + hideFab(); + qrCodeLayout.setVisibility(View.GONE); + } else if (unifiedKeyInfo.has_any_secret()) { + statusImage.setVisibility(View.GONE); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_green); + // reload qr code only if the fingerprint changed + if (!Arrays.equals(unifiedKeyInfo.fingerprint(), qrCodeLoaded)) { + loadQrCode(unifiedKeyInfo.fingerprint()); + } + photoTask.execute(unifiedKeyInfo.master_key_id()); + qrCodeLayout.setVisibility(View.VISIBLE); + + // and place leftOf qr code // RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) // mName.getLayoutParams(); // // remove right margin @@ -878,72 +811,67 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); // mName.setLayoutParams(nameParams); - RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) - statusText.getLayoutParams(); - statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - statusParams.setMarginEnd(0); - } - statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); - statusText.setLayoutParams(statusParams); + RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) + statusText.getLayoutParams(); + statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + statusParams.setMarginEnd(0); + } + statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); + statusText.setLayoutParams(statusParams); - actionEncryptFile.setVisibility(View.VISIBLE); - actionEncryptText.setVisibility(View.VISIBLE); + actionEncryptFile.setVisibility(View.VISIBLE); + actionEncryptText.setVisibility(View.VISIBLE); - showFab(); - // noinspection deprecation (no getDrawable with theme at current minApi level 15!) - floatingActionButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); - } else { - actionEncryptFile.setVisibility(View.VISIBLE); - actionEncryptText.setVisibility(View.VISIBLE); - qrCodeLayout.setVisibility(View.GONE); + showFab(); + // noinspection deprecation (no getDrawable with theme at current minApi level 15!) + floatingActionButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); + } else { + actionEncryptFile.setVisibility(View.VISIBLE); + actionEncryptText.setVisibility(View.VISIBLE); + qrCodeLayout.setVisibility(View.GONE); - if (isVerified) { - statusText.setText(R.string.view_key_verified); - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.VERIFIED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_green); - photoTask.execute(masterKeyId); + if (unifiedKeyInfo.is_verified()) { + statusText.setText(R.string.view_key_verified); + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.VERIFIED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_green); + photoTask.execute(unifiedKeyInfo.master_key_id()); - hideFab(); - } else { - statusText.setText(R.string.view_key_unverified); - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.UNVERIFIED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_orange); + hideFab(); + } else { + statusText.setText(R.string.view_key_unverified); + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.UNVERIFIED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_orange); - showFab(); - } - } - - if (mPreviousColor == 0 || mPreviousColor == color) { - appBarLayout.setBackgroundColor(color); - collapsingToolbarLayout.setContentScrimColor(color); - collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); - mPreviousColor = color; - } else { - ObjectAnimator colorFade = - ObjectAnimator.ofObject(appBarLayout, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); - collapsingToolbarLayout.setContentScrimColor(color); - collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); - - colorFade.setDuration(1200); - colorFade.start(); - mPreviousColor = color; - } - - //noinspection deprecation - statusImage.setAlpha(80); - - break; - } + showFab(); } } + + if (mPreviousColor == 0 || mPreviousColor == color) { + appBarLayout.setBackgroundColor(color); + collapsingToolbarLayout.setContentScrimColor(color); + collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); + mPreviousColor = color; + } else { + ObjectAnimator colorFade = + ObjectAnimator.ofObject(appBarLayout, "backgroundColor", + new ArgbEvaluator(), mPreviousColor, color); + collapsingToolbarLayout.setContentScrimColor(color); + collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); + + colorFade.setDuration(1200); + colorFade.start(); + mPreviousColor = color; + } + + //noinspection deprecation + statusImage.setAlpha(80); } /** @@ -968,16 +896,11 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements floatingActionButton.setVisibility(View.GONE); } - @Override - public void onLoaderReset(Loader loader) { - - } - // CryptoOperationHelper.Callback functions private void updateFromKeyserver() { - if (fingerprint == null) { + if (unifiedKeyInfo == null) { return; } @@ -993,7 +916,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements public ImportKeyringParcel createOperationInput() { HkpKeyserverAddress preferredKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); - ParcelableKeyRing keyEntry = ParcelableKeyRing.createFromReference(fingerprint, null, null, null); + ParcelableKeyRing keyEntry = ParcelableKeyRing.createFromReference(unifiedKeyInfo.fingerprint(), null, null, null); return ImportKeyringParcel.createImportKeyringParcel(Collections.singletonList(keyEntry), preferredKeyserver); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index 07e59d289..f9a68c9be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -26,11 +26,11 @@ import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; import android.os.Handler; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.PopupMenu; -import android.support.v7.widget.PopupMenu.OnDismissListener; import android.support.v7.widget.PopupMenu.OnMenuItemClickListener; import android.view.LayoutInflater; import android.view.MenuItem; @@ -40,8 +40,9 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.model.KeyMetadata; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; +import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity.ViewKeyViewModel; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; @@ -56,12 +57,7 @@ import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView; import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView; -public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, OnMenuItemClickListener { - public static final String ARG_MASTER_KEY_ID = "master_key_id"; - public static final String ARG_IS_SECRET = "is_secret"; - - boolean mIsSecret = false; - +public class ViewKeyFragment extends Fragment implements ViewKeyMvpView, OnMenuItemClickListener { private IdentitiesCardView identitiesCardView; private IdentitiesPresenter identitiesPresenter; @@ -76,32 +72,20 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O private Integer displayedContextMenuPosition; - /** - * Creates new instance of this fragment - */ - public static ViewKeyFragment newInstance(long masterKeyId, boolean isSecret) { - ViewKeyFragment frag = new ViewKeyFragment(); - Bundle args = new Bundle(); - args.putLong(ARG_MASTER_KEY_ID, masterKeyId); - args.putBoolean(ARG_IS_SECRET, isSecret); - - frag.setArguments(args); - - return frag; + public static ViewKeyFragment newInstance() { + return new ViewKeyFragment(); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_fragment, viewGroup, false); identitiesCardView = view.findViewById(R.id.card_identities); - systemContactCard = view.findViewById(R.id.linked_system_contact_card); keyStatusHealth = view.findViewById(R.id.key_status_health); keyStatusKeyserver = view.findViewById(R.id.key_status_keyserver); - return root; + return view; } public static class KeyFragmentViewModel extends ViewModel { @@ -143,39 +127,36 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - long masterKeyId = getArguments().getLong(ARG_MASTER_KEY_ID); - mIsSecret = getArguments().getBoolean(ARG_IS_SECRET); + ViewKeyViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyViewModel.class); + viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); + } + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class); identitiesPresenter = new IdentitiesPresenter( - getContext(), identitiesCardView, this, masterKeyId, mIsSecret); + getContext(), identitiesCardView, this, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); model.getIdentityInfo(identitiesPresenter).observe(this, identitiesPresenter); systemContactPresenter = new SystemContactPresenter( - getContext(), systemContactCard, masterKeyId, mIsSecret); + getContext(), systemContactCard, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); model.getSystemContactInfo(systemContactPresenter).observe(this, systemContactPresenter); - keyHealthPresenter = new KeyHealthPresenter(getContext(), keyStatusHealth, masterKeyId); + keyHealthPresenter = new KeyHealthPresenter(getContext(), keyStatusHealth, unifiedKeyInfo.master_key_id()); model.getSubkeyStatus(keyHealthPresenter).observe(this, keyHealthPresenter); keyserverStatusPresenter = new KeyserverStatusPresenter( - getContext(), keyStatusKeyserver, masterKeyId, mIsSecret); + getContext(), keyStatusKeyserver, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); model.getKeyserverStatus(keyserverStatusPresenter).observe(this, keyserverStatusPresenter); } @Override public void switchToFragment(final Fragment frag, final String backStackName) { - new Handler().post(new Runnable() { - @Override - public void run() { - getFragmentManager().beginTransaction() - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) - .replace(R.id.view_key_fragment, frag) - .addToBackStack(backStackName) - .commit(); - } - }); + new Handler().post(() -> requireFragmentManager().beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(R.id.view_key_fragment, frag) + .addToBackStack(backStackName) + .commit()); } @Override @@ -189,10 +170,6 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O } } - public boolean isValidForData(boolean isSecret) { - return isSecret == mIsSecret; - } - @Override public void startActivityAndShowResultSnackbar(Intent intent) { startActivityForResult(intent, 0); @@ -200,26 +177,18 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O @Override public void showDialogFragment(final DialogFragment dialogFragment, final String tag) { - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - dialogFragment.show(getActivity().getSupportFragmentManager(), tag); - } - }); + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed( + () -> dialogFragment.show(requireFragmentManager(), tag)); } @Override public void showContextMenu(int position, View anchor) { displayedContextMenuPosition = position; - PopupMenu menu = new PopupMenu(getContext(), anchor); + PopupMenu menu = new PopupMenu(requireContext(), anchor); menu.inflate(R.menu.identity_context_menu); menu.setOnMenuItemClickListener(this); - menu.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(PopupMenu popupMenu) { - displayedContextMenuPosition = null; - } - }); + menu.setOnDismissListener(popupMenu -> displayedContextMenuPosition = null); menu.show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java index 9bb3815b4..0b043e91c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java @@ -88,7 +88,6 @@ public class IdentitiesPresenter implements Observer> { @Override public void onChanged(@Nullable List identityInfos) { - viewKeyMvpView.setContentShown(true, false); identitiesAdapter.setData(identityInfos); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java index 3a9e11d63..6c83be8f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.keyview.presenter; import java.util.Comparator; import java.util.Date; -import android.app.Application; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java index b77e4bbc8..5898ae907 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java @@ -30,7 +30,6 @@ public interface ViewKeyMvpView { void startActivity(Intent intent); void startActivityAndShowResultSnackbar(Intent intent); void showDialogFragment(DialogFragment dialogFragment, final String tag); - void setContentShown(boolean show, boolean animate); void showContextMenu(int position, View anchor); } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index 6235a5b73..4653b3ded 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -28,6 +28,7 @@ SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packet (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_encrypt != 0 )) AS has_encrypt_key_int, GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list FROM keys @@ -43,6 +44,7 @@ SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packet (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_encrypt != 0 )) AS has_encrypt_key_int, GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list FROM keys From 8d584df44cb7c4ae8e5066aee5282b1b0f68dffd Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 01:17:39 +0200 Subject: [PATCH 032/124] ditch LoaderFragment --- .../ui/CertifyFingerprintFragment.java | 15 ++- .../keychain/ui/ViewKeyAdvShareFragment.java | 13 +-- .../ui/ViewKeyAdvSubkeysFragment.java | 16 ++-- .../ui/ViewKeyAdvUserIdsFragment.java | 15 ++- .../keychain/ui/ViewKeyKeybaseFragment.java | 16 ++-- .../keychain/ui/base/LoaderFragment.java | 94 ------------------- 6 files changed, 29 insertions(+), 140 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/LoaderFragment.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java index 2c79fee18..e024c6672 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -34,13 +36,11 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import timber.log.Timber; -public class CertifyFingerprintFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks { +public class CertifyFingerprintFragment extends Fragment implements LoaderManager.LoaderCallbacks { static final int REQUEST_CERTIFY = 1; @@ -69,9 +69,8 @@ public class CertifyFingerprintFragment extends LoaderFragment implements } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.certify_fingerprint_fragment, viewGroup, false); TextView actionNo = view.findViewById(R.id.certify_fingerprint_button_no); mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes); @@ -93,7 +92,7 @@ public class CertifyFingerprintFragment extends LoaderFragment implements } }); - return root; + return view; } @Override @@ -127,7 +126,6 @@ public class CertifyFingerprintFragment extends LoaderFragment implements static final int INDEX_UNIFIED_FINGERPRINT = 1; public Loader onCreateLoader(int id, Bundle args) { - setContentShown(false); switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); @@ -161,7 +159,6 @@ public class CertifyFingerprintFragment extends LoaderFragment implements } } - setContentShown(true); } private void displayHexConfirm(byte[] fingerprintBlob) { 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 bf2370ecb..3815af45d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -41,6 +41,7 @@ import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.Fragment; import android.support.v7.widget.CardView; import android.view.LayoutInflater; import android.view.View; @@ -62,7 +63,6 @@ import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -70,7 +70,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import timber.log.Timber; -public class ViewKeyAdvShareFragment extends LoaderFragment { +public class ViewKeyAdvShareFragment extends Fragment { private ImageView mQrCode; private CardView mQrCodeLayout; private TextView mFingerprintView; @@ -79,9 +79,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment { private UnifiedKeyInfo unifiedKeyInfo; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_adv_share_fragment, viewGroup, false); mFingerprintView = view.findViewById(R.id.view_key_fingerprint); mQrCode = view.findViewById(R.id.view_key_qr_code); @@ -132,7 +131,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment { vKeySshClipboardButton.setOnClickListener(v -> shareKey(true, true)); vKeyUploadButton.setOnClickListener(v -> uploadToKeyserver()); - return root; + return view; } private void startSafeSlinger() { @@ -316,8 +315,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment { final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); mFingerprintView.setText(KeyFormattingUtils.formatFingerprint(fingerprint)); - - setContentShown(true); } private void onLoadQrCode(Bitmap qrCode) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index 3ffb268c1..c1984e888 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -26,6 +26,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.view.ActionMode; import android.view.LayoutInflater; @@ -48,13 +50,12 @@ import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; -public class ViewKeyAdvSubkeysFragment extends LoaderFragment { +public class ViewKeyAdvSubkeysFragment extends Fragment { private ListView mSubkeysList; private ListView mSubkeysAddedList; private View mSubkeysAddedLayout; @@ -69,9 +70,8 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment { private UnifiedKeyInfo unifiedKeyInfo; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_adv_subkeys_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_adv_subkeys_fragment, viewGroup, false); mSubkeysList = view.findViewById(R.id.view_key_subkeys); mSubkeysAddedList = view.findViewById(R.id.view_key_subkeys_added); @@ -95,7 +95,7 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment { setHasOptionsMenu(true); - return root; + return view; } @Override @@ -109,8 +109,6 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment { ViewKeyAdvViewModel viewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyAdvViewModel.class); viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadFinished); viewModel.getSubkeyLiveData(requireContext()).observe(this, this::onLoadSubKeys); - - setContentShown(false); } public void onLoadFinished(UnifiedKeyInfo unifiedKeyInfo) { @@ -124,10 +122,8 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment { private void onLoadSubKeys(List subKeys) { mSubkeysAdapter.setData(subKeys); - setContentShown(true); } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mEditKeyHelper != null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index 98b6fe07a..e71cc5917 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -26,6 +26,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.view.ActionMode; import android.view.LayoutInflater; @@ -47,14 +49,13 @@ import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; -public class ViewKeyAdvUserIdsFragment extends LoaderFragment { +public class ViewKeyAdvUserIdsFragment extends Fragment { private ListView mUserIds; private ListView mUserIdsAddedList; private View mUserIdsAddedLayout; @@ -69,9 +70,8 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment { private UnifiedKeyInfo unifiedKeyInfo; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_adv_user_ids_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_adv_user_ids_fragment, viewGroup, false); mUserIds = view.findViewById(R.id.view_key_user_ids); mUserIdsAddedList = view.findViewById(R.id.view_key_user_ids_added); @@ -95,7 +95,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment { setHasOptionsMenu(true); - return root; + return view; } private void showOrEditUserIdInfo(final int position) { @@ -198,8 +198,6 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment { ViewKeyAdvViewModel viewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyAdvViewModel.class); viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); viewModel.getUserIdLiveData(requireContext()).observe(this, this::onLoadUserIds); - - setContentShown(false); } public void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { @@ -211,7 +209,6 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment { private void onLoadUserIds(List userIds) { mUserIdsAdapter.setData(userIds); - setContentShown(true); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java index 3040a710e..3341ee37e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java @@ -28,6 +28,8 @@ import android.graphics.Typeface; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -55,12 +57,11 @@ import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationRes import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; -public class ViewKeyKeybaseFragment extends LoaderFragment implements +public class ViewKeyKeybaseFragment extends Fragment implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { @@ -99,9 +100,8 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, viewGroup, false); mInflater = inflater; mReportHeader = view.findViewById(R.id.view_key_trust_cloud_narrative); @@ -113,7 +113,7 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements mProofVerifyHeader.setVisibility(View.GONE); mProofVerifyDetail.setVisibility(View.GONE); - return root; + return view; } @Override @@ -140,8 +140,6 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements static final int INDEX_VERIFIED = 5; public Loader onCreateLoader(int id, Bundle args) { - setContentShown(false); - switch (id) { case LOADER_ID_DATABASE: { Uri baseUri = KeyRings.buildUnifiedKeyRingUri(masterKeyId); @@ -173,8 +171,6 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements startSearch(fingerprint); } - - setContentShown(true); } private void startSearch(final String fingerprint) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/LoaderFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/LoaderFragment.java deleted file mode 100644 index 27bf1ec1c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/LoaderFragment.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.base; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -import org.sufficientlysecure.keychain.R; - -/** - * This is a fragment helper class, which implements a generic - * progressbar/container view. - *

- * To use it in a fragment, simply subclass, use onCreateView to create the - * layout's root view, and ues getContainer() as root view of your subclass. - * The layout shows a progress bar by default, and can be switched to the - * actual contents by calling setContentShown(). - */ -public abstract class LoaderFragment extends Fragment { - private boolean mContentShown; - private View mProgressContainer; - private ViewGroup mContainer; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.loader_layout, container, false); - - mContentShown = true; - mContainer = root.findViewById(R.id.loader_container); - mProgressContainer = root.findViewById(R.id.loader_progress); - - // content is not shown (by visibility statuses in the layout files) - mContentShown = false; - - return root; - } - - protected ViewGroup getContainer() { - return mContainer; - } - - public void setContentShown(boolean shown, boolean animate) { - if (mContentShown == shown) { - return; - } - mContentShown = shown; - if (shown) { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - mContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - } - mProgressContainer.setVisibility(View.GONE); - mContainer.setVisibility(View.VISIBLE); - } else { - if (animate) { - mProgressContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_in)); - mContainer.startAnimation(AnimationUtils.loadAnimation( - getActivity(), android.R.anim.fade_out)); - } - mProgressContainer.setVisibility(View.VISIBLE); - mContainer.setVisibility(View.INVISIBLE); - } - } - - public void setContentShown(boolean shown) { - setContentShown(shown, true); - } - - public void setContentShownNoAnimation(boolean shown) { - setContentShown(shown, false); - } -} From 38bf42102301368976684dc70bc70d0d97b8818e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 03:10:10 +0200 Subject: [PATCH 033/124] simplify ViewKeyFragment --- .../keychain/ui/adapter/IdentityAdapter.java | 39 +-- .../ui/keyview/KeyFragmentViewModel.java | 70 ++++ .../keychain/ui/keyview/ViewKeyActivity.java | 2 +- .../keychain/ui/keyview/ViewKeyFragment.java | 319 ++++++++++++++---- .../ui/keyview/loader/IdentityDao.java | 31 +- .../ui/keyview/loader/SubkeyStatusDao.java | 112 +++++- .../ui/keyview/loader/SystemContactDao.java | 2 +- .../ui/keyview/loader/ViewKeyLiveData.java | 99 ------ .../presenter/IdentitiesPresenter.java | 154 --------- .../keyview/presenter/KeyHealthPresenter.java | 278 --------------- .../presenter/KeyserverStatusPresenter.java | 78 ----- .../presenter/SystemContactPresenter.java | 90 ----- .../ui/keyview/view/IdentitiesCardView.java | 17 +- .../ui/keyview/view/KeyHealthView.java | 19 +- .../ui/keyview/view/KeyStatusList.java | 6 +- .../ui/keyview/view/KeyserverStatusView.java | 7 +- .../keyview/view/SystemContactCardView.java | 23 +- 17 files changed, 466 insertions(+), 880 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/SystemContactPresenter.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java index 860e2bf22..b8bbd43c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/IdentityAdapter.java @@ -25,10 +25,10 @@ import android.content.Context; import android.graphics.Typeface; import android.os.Build; import android.os.Build.VERSION_CODES; +import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; @@ -52,28 +52,28 @@ public class IdentityAdapter extends RecyclerView.Adapter { private final Context context; private final LayoutInflater layoutInflater; - private final boolean isSecret; private final IdentityClickListener identityClickListener; private List data; + private boolean isSecret; - public IdentityAdapter(Context context, boolean isSecret, IdentityClickListener identityClickListener) { + public IdentityAdapter(Context context, IdentityClickListener identityClickListener) { super(); this.layoutInflater = LayoutInflater.from(context); this.context = context; - this.isSecret = isSecret; this.identityClickListener = identityClickListener; } - public void setData(List data) { + public void setData(List data, boolean isSecret) { this.data = data; + this.isSecret = isSecret; notifyDataSetChanged(); } @Override - public void onBindViewHolder(ViewHolder holder, int position) { + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { IdentityInfo info = data.get(position); int viewType = getItemViewType(position); @@ -90,8 +90,9 @@ public class IdentityAdapter extends RecyclerView.Adapter { } } + @NonNull @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_USER_ID) { return new UserIdViewHolder( layoutInflater.inflate(R.layout.view_key_identity_user_id, parent, false), identityClickListener); @@ -144,12 +145,9 @@ public class IdentityAdapter extends RecyclerView.Adapter { vTitle = view.findViewById(R.id.linked_id_title); vComment = view.findViewById(R.id.linked_id_comment); - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (identityClickListener != null) { - identityClickListener.onClickIdentity(getAdapterPosition()); - } + view.setOnClickListener(v -> { + if (identityClickListener != null) { + identityClickListener.onClickIdentity(getAdapterPosition()); } }); } @@ -213,19 +211,8 @@ public class IdentityAdapter extends RecyclerView.Adapter { vIcon = view.findViewById(R.id.trust_id_app_icon); vMore = view.findViewById(R.id.user_id_item_more); - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - identityClickListener.onClickIdentity(getAdapterPosition()); - } - }); - - vMore.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - identityClickListener.onClickIdentityMore(getAdapterPosition(), v); - } - }); + view.setOnClickListener(v -> identityClickListener.onClickIdentity(getAdapterPosition())); + vMore.setOnClickListener(v -> identityClickListener.onClickIdentityMore(getAdapterPosition(), v)); } public void bind(AutocryptPeerInfo info) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java new file mode 100644 index 000000000..8144caa9c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java @@ -0,0 +1,70 @@ +package org.sufficientlysecure.keychain.ui.keyview; + + +import java.util.List; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Transformations; +import android.arch.lifecycle.ViewModel; +import android.content.Context; + +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.KeyMetadata; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.provider.KeyMetadataDao; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; +import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao; +import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; +import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao; +import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; + + +public class KeyFragmentViewModel extends ViewModel { + private LiveData> identityInfo; + private LiveData subkeyStatus; + private LiveData systemContactInfo; + private LiveData keyserverStatus; + + LiveData> getIdentityInfo(Context context, LiveData unifiedKeyInfoLiveData, + boolean showLinkedIds) { + if (identityInfo == null) { + IdentityDao identityDao = IdentityDao.getInstance(context); + identityInfo = Transformations.switchMap(unifiedKeyInfoLiveData, + (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + () -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id(), showLinkedIds))); + } + return identityInfo; + } + + LiveData getSubkeyStatus(Context context, LiveData unifiedKeyInfoLiveData) { + if (subkeyStatus == null) { + SubkeyStatusDao subkeyStatusDao = SubkeyStatusDao.getInstance(context); + subkeyStatus = Transformations.switchMap(unifiedKeyInfoLiveData, + (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + () -> subkeyStatusDao.getSubkeyStatus(unifiedKeyInfo.master_key_id()))); + } + return subkeyStatus; + } + + LiveData getSystemContactInfo(Context context, LiveData unifiedKeyInfoLiveData) { + if (systemContactInfo == null) { + SystemContactDao systemContactDao = SystemContactDao.getInstance(context); + systemContactInfo = Transformations.switchMap(unifiedKeyInfoLiveData, + (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + () -> systemContactDao.getSystemContactInfo(unifiedKeyInfo.master_key_id(), + unifiedKeyInfo.has_any_secret()))); + } + return systemContactInfo; + } + + LiveData getKeyserverStatus(Context context, LiveData unifiedKeyInfoLiveData) { + if (keyserverStatus == null) { + KeyMetadataDao keyMetadataDao = KeyMetadataDao.create(context); + keyserverStatus = Transformations.switchMap(unifiedKeyInfoLiveData, + (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + () -> keyMetadataDao.getKeyMetadata(unifiedKeyInfo.master_key_id()))); + } + return keyserverStatus; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 052c05a48..2eafd4806 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -313,7 +313,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } if (unifiedKeyInfoLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); - unifiedKeyInfoLiveData = new GenericLiveData<>(context, null, + unifiedKeyInfoLiveData = new GenericLiveData<>(context, KeyRings.buildGenericKeyRingUri(masterKeyId), () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); } return unifiedKeyInfoLiveData; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index f9a68c9be..2a7d83ad8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -21,11 +21,13 @@ package org.sufficientlysecure.keychain.ui.keyview; import java.util.List; import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProviders; +import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; @@ -42,35 +44,43 @@ import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.model.KeyMetadata; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; +import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener; +import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity.ViewKeyViewModel; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo; +import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo; +import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeyHealthStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; +import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.SubKeyItem; import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; -import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter; -import org.sufficientlysecure.keychain.ui.keyview.presenter.SystemContactPresenter; -import org.sufficientlysecure.keychain.ui.keyview.presenter.ViewKeyMvpView; import org.sufficientlysecure.keychain.ui.keyview.view.IdentitiesCardView; import org.sufficientlysecure.keychain.ui.keyview.view.KeyHealthView; +import org.sufficientlysecure.keychain.ui.keyview.view.KeyStatusList.KeyDisplayStatus; import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView; import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; +import org.sufficientlysecure.keychain.util.Preferences; +import timber.log.Timber; -public class ViewKeyFragment extends Fragment implements ViewKeyMvpView, OnMenuItemClickListener { +public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener { private IdentitiesCardView identitiesCardView; - private IdentitiesPresenter identitiesPresenter; + private SystemContactCardView systemContactCard; + private KeyHealthView keyStatusHealth; + private KeyserverStatusView keyserverStatusView; - SystemContactCardView systemContactCard; - SystemContactPresenter systemContactPresenter; - - KeyHealthView keyStatusHealth; - KeyserverStatusView keyStatusKeyserver; - - KeyHealthPresenter keyHealthPresenter; - KeyserverStatusPresenter keyserverStatusPresenter; + IdentityAdapter identitiesAdapter; private Integer displayedContextMenuPosition; + private UnifiedKeyInfo unifiedKeyInfo; + private KeySubkeyStatus subkeyStatus; + private boolean showingExpandedInfo; public static ViewKeyFragment newInstance() { return new ViewKeyFragment(); @@ -83,74 +93,233 @@ public class ViewKeyFragment extends Fragment implements ViewKeyMvpView, OnMenuI identitiesCardView = view.findViewById(R.id.card_identities); systemContactCard = view.findViewById(R.id.linked_system_contact_card); keyStatusHealth = view.findViewById(R.id.key_status_health); - keyStatusKeyserver = view.findViewById(R.id.key_status_keyserver); + keyserverStatusView = view.findViewById(R.id.key_status_keyserver); + + identitiesAdapter = new IdentityAdapter(requireContext(), new IdentityClickListener() { + @Override + public void onClickIdentity(int position) { + showIdentityInfo(position); + } + + @Override + public void onClickIdentityMore(int position, View anchor) { + showIdentityContextMenu(position, anchor); + } + }); + identitiesCardView.setIdentitiesAdapter(identitiesAdapter); + + keyStatusHealth.setOnHealthClickListener((v) -> onKeyHealthClick()); return view; } - public static class KeyFragmentViewModel extends ViewModel { - private LiveData> identityInfo; - private LiveData subkeyStatus; - private LiveData systemContactInfo; - private LiveData keyserverStatus; - - LiveData> getIdentityInfo(IdentitiesPresenter identitiesPresenter) { - if (identityInfo == null) { - identityInfo = identitiesPresenter.getLiveDataInstance(); - } - return identityInfo; - } - - LiveData getSubkeyStatus(KeyHealthPresenter keyHealthPresenter) { - if (subkeyStatus == null) { - subkeyStatus = keyHealthPresenter.getLiveDataInstance(); - } - return subkeyStatus; - } - - LiveData getSystemContactInfo(SystemContactPresenter systemContactPresenter) { - if (systemContactInfo == null) { - systemContactInfo = systemContactPresenter.getLiveDataInstance(); - } - return systemContactInfo; - } - - LiveData getKeyserverStatus(KeyserverStatusPresenter keyserverStatusPresenter) { - if (keyserverStatus == null) { - keyserverStatus = keyserverStatusPresenter.getLiveDataInstance(); - } - return keyserverStatus; - } - } - @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + Context context = requireContext(); + ViewKeyViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyViewModel.class); - viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); + LiveData unifiedKeyInfoLiveData = viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext()); + + unifiedKeyInfoLiveData.observe(this, this::onLoadUnifiedKeyInfo); + + KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class); + + boolean showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities(); + model.getIdentityInfo(context, unifiedKeyInfoLiveData, showLinkedIds).observe(this, this::onLoadIdentityInfo); + model.getKeyserverStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadKeyMetadata); + model.getSystemContactInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSystemContact); + model.getSubkeyStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSubkeyStatus); + } + + private void onLoadSubkeyStatus(KeySubkeyStatus subkeyStatus) { + if (subkeyStatus == null) { + return; + } + + this.subkeyStatus = subkeyStatus; + + KeyHealthStatus keyHealthStatus = subkeyStatus.keyHealthStatus; + + boolean isInsecure = keyHealthStatus == KeyHealthStatus.INSECURE; + boolean isExpired = keyHealthStatus == KeyHealthStatus.EXPIRED; + if (isInsecure) { + boolean primaryKeySecurityProblem = subkeyStatus.keyCertify.mSecurityProblem != null; + if (primaryKeySecurityProblem) { + keyStatusHealth.setKeyStatus(keyHealthStatus); + keyStatusHealth.setPrimarySecurityProblem(subkeyStatus.keyCertify.mSecurityProblem); + keyStatusHealth.setShowExpander(false); + } else { + keyStatusHealth.setKeyStatus(keyHealthStatus); + keyStatusHealth.setShowExpander(false); + displayExpandedInfo(false); + } + } else if (isExpired) { + keyStatusHealth.setKeyStatus(keyHealthStatus); + keyStatusHealth.setPrimaryExpiryDate(subkeyStatus.keyCertify.mExpiry); + keyStatusHealth.setShowExpander(false); + keyStatusHealth.hideExpandedInfo(); + } else { + keyStatusHealth.setKeyStatus(keyHealthStatus); + keyStatusHealth.setShowExpander(keyHealthStatus != KeyHealthStatus.REVOKED); + keyStatusHealth.hideExpandedInfo(); + } + } + + private void displayExpandedInfo(boolean displayAll) { + SubKeyItem keyCertify = subkeyStatus.keyCertify; + SubKeyItem keySign = subkeyStatus.keysSign.isEmpty() ? null : subkeyStatus.keysSign.get(0); + SubKeyItem keyEncrypt = subkeyStatus.keysEncrypt.isEmpty() ? null : subkeyStatus.keysEncrypt.get(0); + + KeyDisplayStatus certDisplayStatus = getKeyDisplayStatus(keyCertify); + KeyDisplayStatus signDisplayStatus = getKeyDisplayStatus(keySign); + KeyDisplayStatus encryptDisplayStatus = getKeyDisplayStatus(keyEncrypt); + + if (!displayAll) { + if (certDisplayStatus == KeyDisplayStatus.OK) { + certDisplayStatus = null; + } + if (certDisplayStatus == KeyDisplayStatus.INSECURE) { + signDisplayStatus = null; + encryptDisplayStatus = null; + } + if (signDisplayStatus == KeyDisplayStatus.OK) { + signDisplayStatus = null; + } + if (encryptDisplayStatus == KeyDisplayStatus.OK) { + encryptDisplayStatus = null; + } + } + + keyStatusHealth.showExpandedState(certDisplayStatus, signDisplayStatus, encryptDisplayStatus); + } + + private void onKeyHealthClick() { + if (showingExpandedInfo) { + showingExpandedInfo = false; + keyStatusHealth.hideExpandedInfo(); + } else { + showingExpandedInfo = true; + displayExpandedInfo(true); + } + } + + private KeyDisplayStatus getKeyDisplayStatus(SubKeyItem subKeyItem) { + if (subKeyItem == null) { + return KeyDisplayStatus.UNAVAILABLE; + } + + if (subKeyItem.mIsRevoked) { + return KeyDisplayStatus.REVOKED; + } + if (subKeyItem.mIsExpired) { + return KeyDisplayStatus.EXPIRED; + } + if (subKeyItem.mSecurityProblem != null) { + return KeyDisplayStatus.INSECURE; + } + if (subKeyItem.mSecretKeyType == SecretKeyType.GNU_DUMMY) { + return KeyDisplayStatus.STRIPPED; + } + if (subKeyItem.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD) { + return KeyDisplayStatus.DIVERT; + } + + return KeyDisplayStatus.OK; } private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { - KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class); + if (unifiedKeyInfo == null) { + return; + } - identitiesPresenter = new IdentitiesPresenter( - getContext(), identitiesCardView, this, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); - model.getIdentityInfo(identitiesPresenter).observe(this, identitiesPresenter); + Context context = requireContext(); - systemContactPresenter = new SystemContactPresenter( - getContext(), systemContactCard, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); - model.getSystemContactInfo(systemContactPresenter).observe(this, systemContactPresenter); + this.unifiedKeyInfo = unifiedKeyInfo; - keyHealthPresenter = new KeyHealthPresenter(getContext(), keyStatusHealth, unifiedKeyInfo.master_key_id()); - model.getSubkeyStatus(keyHealthPresenter).observe(this, keyHealthPresenter); - - keyserverStatusPresenter = new KeyserverStatusPresenter( - getContext(), keyStatusKeyserver, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); - model.getKeyserverStatus(keyserverStatusPresenter).observe(this, keyserverStatusPresenter); + boolean showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities(); + boolean isSecret = unifiedKeyInfo.has_any_secret(); + identitiesCardView.setAddLinkedIdButtonVisible(showLinkedIds && isSecret); + identitiesCardView.setIdentitiesCardListener((v) -> addLinkedIdentity()); + } + + private void showIdentityInfo(final int position) { + IdentityInfo info = identitiesAdapter.getInfo(position); + if (info instanceof LinkedIdInfo) { + showLinkedId((LinkedIdInfo) info); + } else if (info instanceof UserIdInfo) { + showUserIdInfo((UserIdInfo) info); + } else if (info instanceof AutocryptPeerInfo) { + Intent autocryptPeerIntent = ((AutocryptPeerInfo) info).getAutocryptPeerIntent(); + if (autocryptPeerIntent != null) { + startActivity(autocryptPeerIntent); + } + } + } + + private void showIdentityContextMenu(int position, View anchor) { + showContextMenu(position, anchor); + } + + private void showLinkedId(final LinkedIdInfo info) { + LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(info.getMasterKeyId(), info.getRank(), unifiedKeyInfo.has_any_secret()); + + switchToFragment(frag, "linked_id"); + } + + private void showUserIdInfo(UserIdInfo info) { + if (!unifiedKeyInfo.has_any_secret()) { + UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(false, info.isVerified()); + showDialogFragment(dialogFragment, "userIdInfoDialog"); + } + } + + private void addLinkedIdentity() { + Intent intent = new Intent(requireContext(), LinkedIdWizard.class); + intent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + startActivity(intent); + } + + public void onClickForgetIdentity(int position) { + AutocryptPeerInfo info = (AutocryptPeerInfo) identitiesAdapter.getInfo(position); + if (info == null) { + Timber.e("got a 'forget' click on a bad trust id"); + return; + } + + AutocryptPeerDao.getInstance(requireContext()).deleteByIdentifier(info.getPackageName(), info.getIdentity()); + } + + private void onLoadIdentityInfo(List identityInfos) { + identitiesAdapter.setData(identityInfos, unifiedKeyInfo.has_any_secret()); + } + + private void onLoadSystemContact(SystemContactInfo systemContactInfo) { + if (systemContactInfo == null) { + systemContactCard.hideLinkedSystemContact(); + return; + } + + systemContactCard.showLinkedSystemContact(systemContactInfo.contactName, systemContactInfo.contactPicture); + systemContactCard.setSystemContactClickListener((v) -> launchAndroidContactActivity(systemContactInfo.contactId)); + } + + private void onLoadKeyMetadata(KeyMetadata keyMetadata) { + if (keyMetadata == null) { + keyserverStatusView.setDisplayStatusUnknown(); + } else if (keyMetadata.hasBeenUpdated()) { + if (keyMetadata.isPublished()) { + keyserverStatusView.setDisplayStatusPublished(); + } else { + keyserverStatusView.setDisplayStatusNotPublished(); + } + keyserverStatusView.setLastUpdated(keyMetadata.last_updated()); + } else { + keyserverStatusView.setDisplayStatusUnknown(); + } } - @Override public void switchToFragment(final Fragment frag, final String backStackName) { new Handler().post(() -> requireFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) @@ -170,18 +339,11 @@ public class ViewKeyFragment extends Fragment implements ViewKeyMvpView, OnMenuI } } - @Override - public void startActivityAndShowResultSnackbar(Intent intent) { - startActivityForResult(intent, 0); - } - - @Override public void showDialogFragment(final DialogFragment dialogFragment, final String tag) { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed( () -> dialogFragment.show(requireFragmentManager(), tag)); } - @Override public void showContextMenu(int position, View anchor) { displayedContextMenuPosition = position; @@ -202,10 +364,17 @@ public class ViewKeyFragment extends Fragment implements ViewKeyMvpView, OnMenuI case R.id.autocrypt_forget: int position = displayedContextMenuPosition; displayedContextMenuPosition = null; - identitiesPresenter.onClickForgetIdentity(position); + onClickForgetIdentity(position); return true; } return false; } + + private void launchAndroidContactActivity(long contactId) { + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactId)); + intent.setData(uri); + startActivity(intent); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index 0d7c06596..b802c16cd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -71,7 +71,7 @@ public class IdentityDao { this.autocryptPeerDao = autocryptPeerDao; } - List getIdentityInfos(long masterKeyId, boolean showLinkedIds) { + public List getIdentityInfos(long masterKeyId, boolean showLinkedIds) { ArrayList identities = new ArrayList<>(); if (showLinkedIds) { @@ -95,11 +95,11 @@ public class IdentityDao { if (associatedUserIdInfo != null) { int position = identities.indexOf(associatedUserIdInfo); AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo - .create(associatedUserIdInfo, autocryptId, packageName, drawable, autocryptPeerIntent); + .create(masterKeyId, associatedUserIdInfo, autocryptId, packageName, drawable, autocryptPeerIntent); identities.set(position, autocryptPeerInfo); } else { AutocryptPeerInfo autocryptPeerInfo = AutocryptPeerInfo - .create(autocryptId, packageName, drawable, autocryptPeerIntent); + .create(masterKeyId, autocryptId, packageName, drawable, autocryptPeerIntent); identities.add(autocryptPeerInfo); } } @@ -163,7 +163,7 @@ public class IdentityDao { try { UriAttribute uriAttribute = LinkedAttribute.fromAttributeData(userAttribute.attribute_data()); if (uriAttribute instanceof LinkedAttribute) { - return LinkedIdInfo.create(userAttribute.rank(), + return LinkedIdInfo.create(userAttribute.master_key_id(), userAttribute.rank(), userAttribute.isVerified(), userAttribute.is_primary(), (LinkedAttribute) uriAttribute); } } catch (IOException e) { @@ -180,7 +180,7 @@ public class IdentityDao { if (userId.name() != null || userId.email() != null) { IdentityInfo identityInfo = UserIdInfo.create( - userId.rank(), userId.isVerified(), userId.is_primary(), userId.name(), userId.email(), userId.comment()); + userId.master_key_id(), userId.rank(), userId.isVerified(), userId.is_primary(), userId.name(), userId.email(), userId.comment()); identities.add(identityInfo); } } @@ -188,6 +188,7 @@ public class IdentityDao { } public interface IdentityInfo { + long getMasterKeyId(); int getRank(); boolean isVerified(); boolean isPrimary(); @@ -195,6 +196,7 @@ public class IdentityDao { @AutoValue public abstract static class UserIdInfo implements IdentityInfo { + public abstract long getMasterKeyId(); public abstract int getRank(); public abstract boolean isVerified(); public abstract boolean isPrimary(); @@ -206,27 +208,29 @@ public class IdentityDao { @Nullable public abstract String getComment(); - static UserIdInfo create(int rank, boolean isVerified, boolean isPrimary, String name, String email, + static UserIdInfo create(long masterKeyId, int rank, boolean isVerified, boolean isPrimary, String name, String email, String comment) { - return new AutoValue_IdentityDao_UserIdInfo(rank, isVerified, isPrimary, name, email, comment); + return new AutoValue_IdentityDao_UserIdInfo(masterKeyId, rank, isVerified, isPrimary, name, email, comment); } } @AutoValue public abstract static class LinkedIdInfo implements IdentityInfo { + public abstract long getMasterKeyId(); public abstract int getRank(); public abstract boolean isVerified(); public abstract boolean isPrimary(); public abstract LinkedAttribute getLinkedAttribute(); - static LinkedIdInfo create(int rank, boolean isVerified, boolean isPrimary, LinkedAttribute linkedAttribute) { - return new AutoValue_IdentityDao_LinkedIdInfo(rank, isVerified, isPrimary, linkedAttribute); + static LinkedIdInfo create(long masterKeyId, int rank, boolean isVerified, boolean isPrimary, LinkedAttribute linkedAttribute) { + return new AutoValue_IdentityDao_LinkedIdInfo(masterKeyId, rank, isVerified, isPrimary, linkedAttribute); } } @AutoValue public abstract static class AutocryptPeerInfo implements IdentityInfo { + public abstract long getMasterKeyId(); public abstract int getRank(); public abstract boolean isVerified(); public abstract boolean isPrimary(); @@ -240,15 +244,14 @@ public class IdentityDao { @Nullable public abstract Intent getAutocryptPeerIntent(); - static AutocryptPeerInfo create(UserIdInfo userIdInfo, String autocryptPeer, String packageName, + static AutocryptPeerInfo create(long masterKeyId, UserIdInfo userIdInfo, String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityDao_AutocryptPeerInfo(userIdInfo.getRank(), userIdInfo.isVerified(), + return new AutoValue_IdentityDao_AutocryptPeerInfo(masterKeyId, userIdInfo.getRank(), userIdInfo.isVerified(), userIdInfo.isPrimary(), autocryptPeer, packageName, appIcon, userIdInfo, autocryptPeerIntent); } - static AutocryptPeerInfo create(String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityDao_AutocryptPeerInfo( - 0, false, false, autocryptPeer, packageName, appIcon, null, autocryptPeerIntent); + static AutocryptPeerInfo create(long masterKeyId, String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { + return new AutoValue_IdentityDao_AutocryptPeerInfo(masterKeyId,0, false, false, autocryptPeer, packageName, appIcon, null, autocryptPeerIntent); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java index 5ea41d801..494694032 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java @@ -47,7 +47,7 @@ public class SubkeyStatusDao { this.keyRepository = keyRepository; } - KeySubkeyStatus getSubkeyStatus(long masterKeyId, Comparator comparator) { + public KeySubkeyStatus getSubkeyStatus(long masterKeyId) { SubKeyItem keyCertify = null; ArrayList keysSign = new ArrayList<>(); ArrayList keysEncrypt = new ArrayList<>(); @@ -73,10 +73,89 @@ public class SubkeyStatusDao { return null; } - Collections.sort(keysSign, comparator); - Collections.sort(keysEncrypt, comparator); + Collections.sort(keysSign, SUBKEY_COMPARATOR); + Collections.sort(keysEncrypt, SUBKEY_COMPARATOR); - return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt); + KeyHealthStatus keyHealthStatus = determineKeyHealthStatus(keyCertify, keysSign, keysEncrypt); + + return new KeySubkeyStatus(keyCertify, keysSign, keysEncrypt, keyHealthStatus); + } + + private KeyHealthStatus determineKeyHealthStatus(SubKeyItem keyCertify, + ArrayList keysSign, + ArrayList keysEncrypt) { + if (keyCertify.mIsRevoked) { + return KeyHealthStatus.REVOKED; + } + + if (keyCertify.mIsExpired) { + return KeyHealthStatus.EXPIRED; + } + + if (keyCertify.mSecurityProblem != null) { + return KeyHealthStatus.INSECURE; + } + + if (!keysSign.isEmpty() && keysEncrypt.isEmpty()) { + SubKeyItem keySign = keysSign.get(0); + if (!keySign.isValid()) { + return KeyHealthStatus.BROKEN; + } + + if (keySign.mSecurityProblem != null) { + return KeyHealthStatus.INSECURE; + } + + return KeyHealthStatus.SIGN_ONLY; + } + + if (keysSign.isEmpty() || keysEncrypt.isEmpty()) { + return KeyHealthStatus.BROKEN; + } + + SubKeyItem keySign = keysSign.get(0); + SubKeyItem keyEncrypt = keysEncrypt.get(0); + + if (keySign.mSecurityProblem != null && keySign.isValid() + || keyEncrypt.mSecurityProblem != null && keyEncrypt.isValid()) { + return KeyHealthStatus.INSECURE; + } + + if (!keySign.isValid() || !keyEncrypt.isValid()) { + return KeyHealthStatus.BROKEN; + } + + if (keyCertify.mSecretKeyType == SecretKeyType.GNU_DUMMY + && keySign.mSecretKeyType == SecretKeyType.GNU_DUMMY + && keyEncrypt.mSecretKeyType == SecretKeyType.GNU_DUMMY) { + return KeyHealthStatus.STRIPPED; + } + + if (keyCertify.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD + && keySign.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD + && keyEncrypt.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD) { + return KeyHealthStatus.DIVERT; + } + + boolean containsDivertKeys = keyCertify.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD || + keySign.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD || + keyEncrypt.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD; + if (containsDivertKeys) { + return KeyHealthStatus.DIVERT_PARTIAL; + } + + boolean containsStrippedKeys = keyCertify.mSecretKeyType == SecretKeyType.GNU_DUMMY + || keySign.mSecretKeyType == SecretKeyType.GNU_DUMMY + || keyEncrypt.mSecretKeyType == SecretKeyType.GNU_DUMMY; + if (containsStrippedKeys) { + return KeyHealthStatus.PARTIAL_STRIPPED; + } + + return KeyHealthStatus.OK; + } + + public enum KeyHealthStatus { + OK, DIVERT, DIVERT_PARTIAL, REVOKED, EXPIRED, INSECURE, SIGN_ONLY, STRIPPED, PARTIAL_STRIPPED, BROKEN } public static class KeySubkeyStatus { @@ -84,11 +163,14 @@ public class SubkeyStatusDao { public final SubKeyItem keyCertify; public final List keysSign; public final List keysEncrypt; + public final KeyHealthStatus keyHealthStatus; - KeySubkeyStatus(@NonNull SubKeyItem keyCertify, List keysSign, List keysEncrypt) { + KeySubkeyStatus(@NonNull SubKeyItem keyCertify, List keysSign, List keysEncrypt, + KeyHealthStatus keyHealthStatus) { this.keyCertify = keyCertify; this.keysSign = keysSign; this.keysEncrypt = keysEncrypt; + this.keyHealthStatus = keyHealthStatus; } } @@ -131,4 +213,24 @@ public class SubkeyStatusDao { return !mIsRevoked && !mIsExpired; } } + + private static final Comparator SUBKEY_COMPARATOR = (one, two) -> { + if (one == two) { + return 0; + } + // if one is valid and the other isn't, the valid one always comes first + if (one.isValid() ^ two.isValid()) { + return one.isValid() ? -1 : 1; + } + // compare usability, if one is "more usable" than the other, that one comes first + int usability = one.mSecretKeyType.compareUsability(two.mSecretKeyType); + if (usability != 0) { + return usability; + } + if ((one.mSecurityProblem == null) ^ (two.mSecurityProblem == null)) { + return one.mSecurityProblem == null ? -1 : 1; + } + // otherwise, the newer one comes first + return one.newerThan(two) ? -1 : 1; + }; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java index e666920c4..499f36347 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java @@ -60,7 +60,7 @@ public class SystemContactDao { this.contentResolver = contentResolver; } - SystemContactInfo getSystemContactInfo(long masterKeyId, boolean isSecret) { + public SystemContactInfo getSystemContactInfo(long masterKeyId, boolean isSecret) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED) { Timber.w(Constants.TAG, "loading linked system contact not possible READ_CONTACTS permission denied!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java deleted file mode 100644 index 8d09591ea..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.sufficientlysecure.keychain.ui.keyview.loader; - - -import java.util.Comparator; -import java.util.List; - -import android.content.Context; - -import org.sufficientlysecure.keychain.model.KeyMetadata; -import org.sufficientlysecure.keychain.provider.KeyMetadataDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.SubKeyItem; -import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; - - -public class ViewKeyLiveData { - public static class IdentityLiveData extends AsyncTaskLiveData> { - private final IdentityDao identityDao; - - private final long masterKeyId; - private final boolean showLinkedIds; - - public IdentityLiveData(Context context, long masterKeyId, boolean showLinkedIds) { - super(context, KeyRings.buildGenericKeyRingUri(masterKeyId)); - - this.identityDao = IdentityDao.getInstance(context); - - this.masterKeyId = masterKeyId; - this.showLinkedIds = showLinkedIds; - } - - @Override - public List asyncLoadData() { - return identityDao.getIdentityInfos(masterKeyId, showLinkedIds); - } - } - - public static class SubkeyStatusLiveData extends AsyncTaskLiveData { - private final SubkeyStatusDao subkeyStatusDao; - - private final long masterKeyId; - private final Comparator comparator; - - public SubkeyStatusLiveData(Context context, long masterKeyId, Comparator comparator) { - super(context, KeyRings.buildGenericKeyRingUri(masterKeyId)); - - this.subkeyStatusDao = SubkeyStatusDao.getInstance(context); - - this.masterKeyId = masterKeyId; - this.comparator = comparator; - } - - @Override - public KeySubkeyStatus asyncLoadData() { - return subkeyStatusDao.getSubkeyStatus(masterKeyId, comparator); - } - } - - public static class SystemContactInfoLiveData extends AsyncTaskLiveData { - private final SystemContactDao systemContactDao; - - private final long masterKeyId; - private final boolean isSecret; - - public SystemContactInfoLiveData(Context context, long masterKeyId, boolean isSecret) { - super(context, null); - - this.systemContactDao = SystemContactDao.getInstance(context); - - this.masterKeyId = masterKeyId; - this.isSecret = isSecret; - } - - @Override - public SystemContactInfo asyncLoadData() { - return systemContactDao.getSystemContactInfo(masterKeyId, isSecret); - } - } - - public static class KeyserverStatusLiveData extends AsyncTaskLiveData { - private final KeyMetadataDao keyMetadataDao; - - private final long masterKeyId; - - public KeyserverStatusLiveData(Context context, long masterKeyId) { - super(context, KeyRings.buildGenericKeyRingUri(masterKeyId)); - - this.keyMetadataDao = KeyMetadataDao.create(context); - this.masterKeyId = masterKeyId; - } - - @Override - public KeyMetadata asyncLoadData() { - return keyMetadataDao.getKeyMetadata(masterKeyId); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java deleted file mode 100644 index 0b043e91c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.keyview.presenter; - - -import java.util.List; - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.Observer; -import android.content.Context; -import android.content.Intent; -import android.support.annotation.Nullable; -import android.view.View; - -import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; -import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener; -import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; -import org.sufficientlysecure.keychain.ui.keyview.LinkedIdViewFragment; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.ViewKeyLiveData.IdentityLiveData; -import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; -import org.sufficientlysecure.keychain.util.Preferences; -import timber.log.Timber; - - -public class IdentitiesPresenter implements Observer> { - private final Context context; - private final IdentitiesMvpView view; - private final ViewKeyMvpView viewKeyMvpView; - - private final IdentityAdapter identitiesAdapter; - - private final long masterKeyId; - private final boolean isSecret; - private final boolean showLinkedIds; - private AutocryptPeerDao autocryptPeerDao; - - public IdentitiesPresenter(Context context, IdentitiesMvpView view, ViewKeyMvpView viewKeyMvpView, - long masterKeyId, boolean isSecret) { - this.context = context; - this.view = view; - this.viewKeyMvpView = viewKeyMvpView; - this.autocryptPeerDao = AutocryptPeerDao.getInstance(context); - - this.masterKeyId = masterKeyId; - this.isSecret = isSecret; - - showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities(); - - identitiesAdapter = new IdentityAdapter(context, isSecret, new IdentityClickListener() { - @Override - public void onClickIdentity(int position) { - showIdentityInfo(position); - } - - @Override - public void onClickIdentityMore(int position, View anchor) { - showIdentityContextMenu(position, anchor); - - } - }); - view.setIdentitiesAdapter(identitiesAdapter); - - view.setAddLinkedIdButtonVisible(showLinkedIds && isSecret); - - view.setIdentitiesCardListener(() -> addLinkedIdentity()); - } - - @Override - public void onChanged(@Nullable List identityInfos) { - identitiesAdapter.setData(identityInfos); - } - - private void showIdentityInfo(final int position) { - IdentityInfo info = identitiesAdapter.getInfo(position); - if (info instanceof LinkedIdInfo) { - showLinkedId((LinkedIdInfo) info); - } else if (info instanceof UserIdInfo) { - showUserIdInfo((UserIdInfo) info); - } else if (info instanceof AutocryptPeerInfo) { - Intent autocryptPeerIntent = ((AutocryptPeerInfo) info).getAutocryptPeerIntent(); - if (autocryptPeerIntent != null) { - viewKeyMvpView.startActivity(autocryptPeerIntent); - } - } - } - - private void showIdentityContextMenu(int position, View anchor) { - viewKeyMvpView.showContextMenu(position, anchor); - } - - private void showLinkedId(final LinkedIdInfo info) { - LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(masterKeyId, info.getRank(), isSecret); - - viewKeyMvpView.switchToFragment(frag, "linked_id"); - } - - private void showUserIdInfo(UserIdInfo info) { - if (!isSecret) { - UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(false, info.isVerified()); - viewKeyMvpView.showDialogFragment(dialogFragment, "userIdInfoDialog"); - } - } - - private void addLinkedIdentity() { - Intent intent = new Intent(context, LinkedIdWizard.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); - context.startActivity(intent); - } - - public void onClickForgetIdentity(int position) { - AutocryptPeerInfo info = (AutocryptPeerInfo) identitiesAdapter.getInfo(position); - if (info == null) { - Timber.e("got a 'forget' click on a bad trust id"); - return; - } - - autocryptPeerDao.deleteByIdentifier(info.getPackageName(), info.getIdentity()); - } - - public LiveData> getLiveDataInstance() { - return new IdentityLiveData(context, masterKeyId, showLinkedIds); - } - - public interface IdentitiesMvpView { - void setIdentitiesAdapter(IdentityAdapter userIdsAdapter); - void setIdentitiesCardListener(IdentitiesCardListener identitiesCardListener); - void setAddLinkedIdButtonVisible(boolean showLinkedIds); - } - - public interface IdentitiesCardListener { - void onClickAddIdentity(); - } -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java deleted file mode 100644 index 6c83be8f7..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.keyview.presenter; - - -import java.util.Comparator; -import java.util.Date; - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.Observer; -import android.content.Context; -import android.support.annotation.Nullable; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.SubKeyItem; -import org.sufficientlysecure.keychain.ui.keyview.loader.ViewKeyLiveData.SubkeyStatusLiveData; -import org.sufficientlysecure.keychain.ui.keyview.view.KeyStatusList.KeyDisplayStatus; - - -public class KeyHealthPresenter implements Observer { - private static final Comparator SUBKEY_COMPARATOR = new Comparator() { - @Override - public int compare(SubKeyItem one, SubKeyItem two) { - // if one is valid and the other isn't, the valid one always comes first - if (one.isValid() ^ two.isValid()) { - return one.isValid() ? -1 : 1; - } - // compare usability, if one is "more usable" than the other, that one comes first - int usability = one.mSecretKeyType.compareUsability(two.mSecretKeyType); - if (usability != 0) { - return usability; - } - if ((one.mSecurityProblem == null) ^ (two.mSecurityProblem == null)) { - return one.mSecurityProblem == null ? -1 : 1; - } - // otherwise, the newer one comes first - return one.newerThan(two) ? -1 : 1; - } - }; - - private final Context context; - private final KeyHealthMvpView view; - private final long masterKeyId; - - private KeySubkeyStatus subkeyStatus; - private boolean showingExpandedInfo; - - - public KeyHealthPresenter(Context context, KeyHealthMvpView view, long masterKeyId) { - this.context = context; - this.view = view; - this.masterKeyId = masterKeyId; - - view.setOnHealthClickListener(new KeyHealthClickListener() { - @Override - public void onKeyHealthClick() { - KeyHealthPresenter.this.onKeyHealthClick(); - } - }); - } - - @Override - public void onChanged(@Nullable KeySubkeyStatus subkeyStatus) { - this.subkeyStatus = subkeyStatus; - if (subkeyStatus == null) { - return; - } - - KeyHealthStatus keyHealthStatus = determineKeyHealthStatus(subkeyStatus); - - boolean isInsecure = keyHealthStatus == KeyHealthStatus.INSECURE; - boolean isExpired = keyHealthStatus == KeyHealthStatus.EXPIRED; - if (isInsecure) { - boolean primaryKeySecurityProblem = subkeyStatus.keyCertify.mSecurityProblem != null; - if (primaryKeySecurityProblem) { - view.setKeyStatus(keyHealthStatus); - view.setPrimarySecurityProblem(subkeyStatus.keyCertify.mSecurityProblem); - view.setShowExpander(false); - } else { - view.setKeyStatus(keyHealthStatus); - view.setShowExpander(false); - displayExpandedInfo(false); - } - } else if (isExpired) { - view.setKeyStatus(keyHealthStatus); - view.setPrimaryExpiryDate(subkeyStatus.keyCertify.mExpiry); - view.setShowExpander(false); - view.hideExpandedInfo(); - } else { - view.setKeyStatus(keyHealthStatus); - view.setShowExpander(keyHealthStatus != KeyHealthStatus.REVOKED); - view.hideExpandedInfo(); - } - } - - private KeyHealthStatus determineKeyHealthStatus(KeySubkeyStatus subkeyStatus) { - SubKeyItem keyCertify = subkeyStatus.keyCertify; - if (keyCertify.mIsRevoked) { - return KeyHealthStatus.REVOKED; - } - - if (keyCertify.mIsExpired) { - return KeyHealthStatus.EXPIRED; - } - - if (keyCertify.mSecurityProblem != null) { - return KeyHealthStatus.INSECURE; - } - - if (!subkeyStatus.keysSign.isEmpty() && subkeyStatus.keysEncrypt.isEmpty()) { - SubKeyItem keySign = subkeyStatus.keysSign.get(0); - if (!keySign.isValid()) { - return KeyHealthStatus.BROKEN; - } - - if (keySign.mSecurityProblem != null) { - return KeyHealthStatus.INSECURE; - } - - return KeyHealthStatus.SIGN_ONLY; - } - - if (subkeyStatus.keysSign.isEmpty() || subkeyStatus.keysEncrypt.isEmpty()) { - return KeyHealthStatus.BROKEN; - } - - SubKeyItem keySign = subkeyStatus.keysSign.get(0); - SubKeyItem keyEncrypt = subkeyStatus.keysEncrypt.get(0); - - if (keySign.mSecurityProblem != null && keySign.isValid() - || keyEncrypt.mSecurityProblem != null && keyEncrypt.isValid()) { - return KeyHealthStatus.INSECURE; - } - - if (!keySign.isValid() || !keyEncrypt.isValid()) { - return KeyHealthStatus.BROKEN; - } - - if (keyCertify.mSecretKeyType == SecretKeyType.GNU_DUMMY - && keySign.mSecretKeyType == SecretKeyType.GNU_DUMMY - && keyEncrypt.mSecretKeyType == SecretKeyType.GNU_DUMMY) { - return KeyHealthStatus.STRIPPED; - } - - if (keyCertify.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD - && keySign.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD - && keyEncrypt.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD) { - return KeyHealthStatus.DIVERT; - } - - boolean containsDivertKeys = keyCertify.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD || - keySign.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD || - keyEncrypt.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD; - if (containsDivertKeys) { - return KeyHealthStatus.DIVERT_PARTIAL; - } - - boolean containsStrippedKeys = keyCertify.mSecretKeyType == SecretKeyType.GNU_DUMMY - || keySign.mSecretKeyType == SecretKeyType.GNU_DUMMY - || keyEncrypt.mSecretKeyType == SecretKeyType.GNU_DUMMY; - if (containsStrippedKeys) { - return KeyHealthStatus.PARTIAL_STRIPPED; - } - - return KeyHealthStatus.OK; - } - - private void onKeyHealthClick() { - if (showingExpandedInfo) { - showingExpandedInfo = false; - view.hideExpandedInfo(); - } else { - showingExpandedInfo = true; - displayExpandedInfo(true); - } - } - - private void displayExpandedInfo(boolean displayAll) { - SubKeyItem keyCertify = subkeyStatus.keyCertify; - SubKeyItem keySign = subkeyStatus.keysSign.isEmpty() ? null : subkeyStatus.keysSign.get(0); - SubKeyItem keyEncrypt = subkeyStatus.keysEncrypt.isEmpty() ? null : subkeyStatus.keysEncrypt.get(0); - - KeyDisplayStatus certDisplayStatus = getKeyDisplayStatus(keyCertify); - KeyDisplayStatus signDisplayStatus = getKeyDisplayStatus(keySign); - KeyDisplayStatus encryptDisplayStatus = getKeyDisplayStatus(keyEncrypt); - - if (!displayAll) { - if (certDisplayStatus == KeyDisplayStatus.OK) { - certDisplayStatus = null; - } - if (certDisplayStatus == KeyDisplayStatus.INSECURE) { - signDisplayStatus = null; - encryptDisplayStatus = null; - } - if (signDisplayStatus == KeyDisplayStatus.OK) { - signDisplayStatus = null; - } - if (encryptDisplayStatus == KeyDisplayStatus.OK) { - encryptDisplayStatus = null; - } - } - - view.showExpandedState(certDisplayStatus, signDisplayStatus, encryptDisplayStatus); - } - - private KeyDisplayStatus getKeyDisplayStatus(SubKeyItem subKeyItem) { - if (subKeyItem == null) { - return KeyDisplayStatus.UNAVAILABLE; - } - - if (subKeyItem.mIsRevoked) { - return KeyDisplayStatus.REVOKED; - } - if (subKeyItem.mIsExpired) { - return KeyDisplayStatus.EXPIRED; - } - if (subKeyItem.mSecurityProblem != null) { - return KeyDisplayStatus.INSECURE; - } - if (subKeyItem.mSecretKeyType == SecretKeyType.GNU_DUMMY) { - return KeyDisplayStatus.STRIPPED; - } - if (subKeyItem.mSecretKeyType == SecretKeyType.DIVERT_TO_CARD) { - return KeyDisplayStatus.DIVERT; - } - - return KeyDisplayStatus.OK; - } - - public LiveData getLiveDataInstance() { - return new SubkeyStatusLiveData(context, masterKeyId, SUBKEY_COMPARATOR); - } - - public enum KeyHealthStatus { - OK, DIVERT, DIVERT_PARTIAL, REVOKED, EXPIRED, INSECURE, SIGN_ONLY, STRIPPED, PARTIAL_STRIPPED, BROKEN - } - - public interface KeyHealthMvpView { - void setKeyStatus(KeyHealthStatus keyHealthStatus); - void setPrimarySecurityProblem(KeySecurityProblem securityProblem); - void setPrimaryExpiryDate(Date expiry); - - void setShowExpander(boolean showExpander); - void showExpandedState(KeyDisplayStatus certifyStatus, KeyDisplayStatus signStatus, - KeyDisplayStatus encryptStatus); - void hideExpandedInfo(); - - void setOnHealthClickListener(KeyHealthClickListener keyHealthClickListener); - - } - - public interface KeyStatusMvpView { - void setCertifyStatus(KeyDisplayStatus unavailable); - void setSignStatus(KeyDisplayStatus signStatus); - void setDecryptStatus(KeyDisplayStatus encryptStatus); - } - - public interface KeyHealthClickListener { - void onKeyHealthClick(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java deleted file mode 100644 index c0548848f..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.keyview.presenter; - - -import java.util.Date; - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.Observer; -import android.content.Context; -import android.support.annotation.Nullable; - -import org.sufficientlysecure.keychain.model.KeyMetadata; -import org.sufficientlysecure.keychain.ui.keyview.loader.ViewKeyLiveData.KeyserverStatusLiveData; - - -public class KeyserverStatusPresenter implements Observer { - private final Context context; - private final KeyserverStatusMvpView view; - - private final long masterKeyId; - private final boolean isSecret; - - - public KeyserverStatusPresenter(Context context, KeyserverStatusMvpView view, long masterKeyId, - boolean isSecret) { - this.context = context; - this.view = view; - - this.masterKeyId = masterKeyId; - this.isSecret = isSecret; - } - - public LiveData getLiveDataInstance() { - return new KeyserverStatusLiveData(context, masterKeyId); - } - - @Override - public void onChanged(@Nullable KeyMetadata keyserverStatus) { - if (keyserverStatus == null) { - view.setDisplayStatusUnknown(); - return; - } - - if (keyserverStatus.hasBeenUpdated()) { - if (keyserverStatus.isPublished()) { - view.setDisplayStatusPublished(); - } else { - view.setDisplayStatusNotPublished(); - } - view.setLastUpdated(keyserverStatus.last_updated()); - } else { - view.setDisplayStatusUnknown(); - } - } - - public interface KeyserverStatusMvpView { - void setDisplayStatusPublished(); - void setDisplayStatusNotPublished(); - void setLastUpdated(Date lastUpdated); - void setDisplayStatusUnknown(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/SystemContactPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/SystemContactPresenter.java deleted file mode 100644 index 4c4dfbd43..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/SystemContactPresenter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.keyview.presenter; - - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.Observer; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.net.Uri; -import android.provider.ContactsContract; -import android.support.annotation.Nullable; - -import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.ViewKeyLiveData.SystemContactInfoLiveData; - - -public class SystemContactPresenter implements Observer { - private final Context context; - private final SystemContactMvpView view; - - private final long masterKeyId; - private final boolean isSecret; - - private long contactId; - - - public SystemContactPresenter(Context context, SystemContactMvpView view, long masterKeyId, boolean isSecret) { - this.context = context; - this.view = view; - - this.masterKeyId = masterKeyId; - this.isSecret = isSecret; - - view.setSystemContactClickListener(SystemContactPresenter.this::onSystemContactClick); - } - - public LiveData getLiveDataInstance() { - return new SystemContactInfoLiveData(context, masterKeyId, isSecret); - } - - @Override - public void onChanged(@Nullable SystemContactInfo systemContactInfo) { - if (systemContactInfo == null) { - view.hideLinkedSystemContact(); - return; - } - - this.contactId = systemContactInfo.contactId; - view.showLinkedSystemContact(systemContactInfo.contactName, systemContactInfo.contactPicture); - } - - private void onSystemContactClick() { - launchAndroidContactActivity(contactId, context); - } - - public interface SystemContactMvpView { - void setSystemContactClickListener(SystemContactClickListener systemContactClickListener); - - void showLinkedSystemContact(String contactName, Bitmap picture); - void hideLinkedSystemContact(); - } - - public interface SystemContactClickListener { - void onSystemContactClick(); - } - - private static void launchAndroidContactActivity(long contactId, Context context) { - Intent intent = new Intent(Intent.ACTION_VIEW); - Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactId)); - intent.setData(uri); - context.startActivity(intent); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/IdentitiesCardView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/IdentitiesCardView.java index 26d8b5801..98b7ccdba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/IdentitiesCardView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/IdentitiesCardView.java @@ -29,15 +29,12 @@ import android.widget.Button; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; -import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter.IdentitiesCardListener; -import org.sufficientlysecure.keychain.ui.keyview.presenter.IdentitiesPresenter.IdentitiesMvpView; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; -public class IdentitiesCardView extends CardView implements IdentitiesMvpView { +public class IdentitiesCardView extends CardView { private final RecyclerView vIdentities; - private IdentitiesCardListener identitiesCardListener; private final Button linkedIdsAddButton; public IdentitiesCardView(Context context, AttributeSet attrs) { @@ -50,24 +47,16 @@ public class IdentitiesCardView extends CardView implements IdentitiesMvpView { vIdentities.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST, false)); linkedIdsAddButton = view.findViewById(R.id.view_key_card_linked_ids_add); - linkedIdsAddButton.setOnClickListener(v -> { - if (identitiesCardListener != null) { - identitiesCardListener.onClickAddIdentity(); - } - }); } - @Override public void setIdentitiesAdapter(IdentityAdapter identityAdapter) { vIdentities.setAdapter(identityAdapter); } - @Override - public void setIdentitiesCardListener(IdentitiesCardListener identitiesCardListener) { - this.identitiesCardListener = identitiesCardListener; + public void setIdentitiesCardListener(OnClickListener identitiesCardListener) { + linkedIdsAddButton.setOnClickListener(identitiesCardListener); } - @Override public void setAddLinkedIdButtonVisible(boolean show) { linkedIdsAddButton.setVisibility(show ? View.VISIBLE : View.GONE); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyHealthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyHealthView.java index 191c6c6da..9b94195b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyHealthView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyHealthView.java @@ -39,14 +39,12 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength; import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.NotWhitelistedCurve; import org.sufficientlysecure.keychain.pgp.SecurityProblem.UnidentifiedKeyProblem; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter.KeyHealthClickListener; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter.KeyHealthMvpView; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter.KeyHealthStatus; +import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeyHealthStatus; import org.sufficientlysecure.keychain.ui.keyview.view.KeyStatusList.KeyDisplayStatus; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnClickListener { +public class KeyHealthView extends LinearLayout implements OnClickListener { private final View vLayout; private final TextView vTitle, vSubtitle; private final ImageView vIcon; @@ -59,7 +57,7 @@ public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnC private final View vExpiryLayout; private final TextView vExpiryText; - private KeyHealthClickListener keyHealthClickListener; + private OnClickListener keyHealthClickListener; public KeyHealthView(Context context, AttributeSet attrs) { super(context, attrs); @@ -125,7 +123,6 @@ public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnC } } - @Override public void setKeyStatus(KeyHealthStatus keyHealthStatus) { switch (keyHealthStatus) { case OK: @@ -161,7 +158,6 @@ public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnC } } - @Override public void setPrimarySecurityProblem(KeySecurityProblem securityProblem) { if (securityProblem == null) { vInsecureLayout.setVisibility(View.GONE); @@ -190,7 +186,6 @@ public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnC } - @Override public void setPrimaryExpiryDate(Date expiry) { if (expiry == null) { vExpiryLayout.setVisibility(View.GONE); @@ -205,23 +200,20 @@ public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnC @Override public void onClick(View view) { if (keyHealthClickListener != null) { - keyHealthClickListener.onKeyHealthClick(); + keyHealthClickListener.onClick(view); } } - @Override - public void setOnHealthClickListener(KeyHealthClickListener keyHealthClickListener) { + public void setOnHealthClickListener(OnClickListener keyHealthClickListener) { this.keyHealthClickListener = keyHealthClickListener; vLayout.setClickable(keyHealthClickListener != null); } - @Override public void setShowExpander(boolean showExpander) { vLayout.setClickable(showExpander); vExpander.setVisibility(showExpander ? View.VISIBLE : View.GONE); } - @Override public void showExpandedState(KeyDisplayStatus certifyStatus, KeyDisplayStatus signStatus, KeyDisplayStatus encryptStatus) { if (certifyStatus == null && signStatus == null && encryptStatus == null) { @@ -240,7 +232,6 @@ public class KeyHealthView extends LinearLayout implements KeyHealthMvpView, OnC } - @Override public void hideExpandedInfo() { showExpandedState(null, null, null); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyStatusList.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyStatusList.java index e1a0c3e6a..b4bb2669d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyStatusList.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyStatusList.java @@ -30,10 +30,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter.KeyStatusMvpView; -public class KeyStatusList extends LinearLayout implements KeyStatusMvpView { +public class KeyStatusList extends LinearLayout { private final TextView vCertText, vSignText, vDecryptText; private final ImageView vCertIcon, vSignIcon, vDecryptIcon; private final View vCertToken, vSignToken, vDecryptToken; @@ -107,7 +106,6 @@ public class KeyStatusList extends LinearLayout implements KeyStatusMvpView { } - @Override public void setCertifyStatus(KeyDisplayStatus keyDisplayStatus) { if (keyDisplayStatus == null) { vCertifyLayout.setVisibility(View.GONE); @@ -121,7 +119,6 @@ public class KeyStatusList extends LinearLayout implements KeyStatusMvpView { vCertifyLayout.setVisibility(View.VISIBLE); } - @Override public void setSignStatus(KeyDisplayStatus keyDisplayStatus) { if (keyDisplayStatus == null) { vSignLayout.setVisibility(View.GONE); @@ -134,7 +131,6 @@ public class KeyStatusList extends LinearLayout implements KeyStatusMvpView { vSignLayout.setVisibility(View.VISIBLE); } - @Override public void setDecryptStatus(KeyDisplayStatus keyDisplayStatus) { if (keyDisplayStatus == null) { vDecryptLayout.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusView.java index 641153bc1..1b353e729 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusView.java @@ -34,10 +34,9 @@ import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter.KeyserverStatusMvpView; -public class KeyserverStatusView extends FrameLayout implements KeyserverStatusMvpView { +public class KeyserverStatusView extends FrameLayout { private final View vLayout; private final TextView vTitle; private final TextView vSubtitle; @@ -75,23 +74,19 @@ public class KeyserverStatusView extends FrameLayout implements KeyserverStatusM } } - @Override public void setDisplayStatusPublished() { setDisplayStatus(KeyserverDisplayStatus.PUBLISHED); } - @Override public void setDisplayStatusNotPublished() { setDisplayStatus(KeyserverDisplayStatus.NOT_PUBLISHED); } - @Override public void setDisplayStatusUnknown() { setDisplayStatus(KeyserverDisplayStatus.UNKNOWN); vSubtitle.setText(R.string.keyserver_last_updated_never); } - @Override public void setLastUpdated(Date lastUpdated) { String lastUpdatedText = DateFormat.getMediumDateFormat(getContext()).format(lastUpdated); vSubtitle.setText(getResources().getString(R.string.keyserver_last_updated, lastUpdatedText)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/SystemContactCardView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/SystemContactCardView.java index 61fb46ed8..2cfd01440 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/SystemContactCardView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/SystemContactCardView.java @@ -24,23 +24,18 @@ import android.support.v7.widget.CardView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.keyview.presenter.SystemContactPresenter.SystemContactClickListener; -import org.sufficientlysecure.keychain.ui.keyview.presenter.SystemContactPresenter.SystemContactMvpView; -public class SystemContactCardView extends CardView implements SystemContactMvpView, OnClickListener { +public class SystemContactCardView extends CardView { private LinearLayout vSystemContactLayout; private ImageView vSystemContactPicture; private TextView vSystemContactName; - private SystemContactClickListener systemContactClickListener; - public SystemContactCardView(Context context, AttributeSet attrs) { super(context, attrs); @@ -49,28 +44,16 @@ public class SystemContactCardView extends CardView implements SystemContactMvpV vSystemContactLayout = view.findViewById(R.id.system_contact_layout); vSystemContactName = view.findViewById(R.id.system_contact_name); vSystemContactPicture = view.findViewById(R.id.system_contact_picture); - - vSystemContactLayout.setOnClickListener(this); } - @Override - public void onClick(View view) { - if (systemContactClickListener != null) { - systemContactClickListener.onSystemContactClick(); - } - } - - @Override - public void setSystemContactClickListener(SystemContactClickListener systemContactClickListener) { - this.systemContactClickListener = systemContactClickListener; - vSystemContactLayout.setClickable(systemContactClickListener != null); + public void setSystemContactClickListener(OnClickListener onClickListener) { + vSystemContactLayout.setOnClickListener(onClickListener); } public void hideLinkedSystemContact() { setVisibility(View.GONE); } - @Override public void showLinkedSystemContact(String contactName, Bitmap picture) { vSystemContactName.setText(contactName); if (picture != null) { From 2a5b93d9c50e3623b4d147c3615e4389ce8d7bcf Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 11:21:52 +0200 Subject: [PATCH 034/124] fix unit tests --- .../keychain/pgp/PgpEncryptDecryptTest.java | 8 ++------ .../sufficientlysecure/keychain/provider/InteropTest.java | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 943446728..0a6f03932 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -831,9 +831,7 @@ public class PgpEncryptDecryptTest { { // decryption with passphrase cached should succeed for the other key if first is gone // delete first key from database - KeyWritableRepository.create(RuntimeEnvironment.application).getContentResolver().delete( - KeyRingData.buildPublicKeyRingUri(mStaticRing1.getMasterKeyId()), null, null - ); + KeyWritableRepository.create(RuntimeEnvironment.application).deleteKeyRing(mStaticRing1.getMasterKeyId()); ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); @@ -912,9 +910,7 @@ public class PgpEncryptDecryptTest { { // decryption with passphrase cached should succeed for the other key if first is gone // delete first key from database - KeyWritableRepository.create(RuntimeEnvironment.application).getContentResolver().delete( - KeyRingData.buildPublicKeyRingUri(mStaticRing1.getMasterKeyId()), null, null - ); + KeyWritableRepository.create(RuntimeEnvironment.application).deleteKeyRing(mStaticRing1.getMasterKeyId()); ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = new ByteArrayInputStream(ciphertext); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index 6101b4a02..384f83949 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -244,6 +244,7 @@ public class InteropTest { KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(verify.getMasterKeyId()) : null; KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application, + new KeychainDatabase(RuntimeEnvironment.application), LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application), LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application), DatabaseNotifyManager.create(RuntimeEnvironment.application), From 4fcc5253ae12b5ff408e264621784fa3d3d59aae Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 12:41:13 +0200 Subject: [PATCH 035/124] use LiveData in LinkedIdWizard and related Fragments --- .../keychain/provider/KeychainContract.java | 5 - .../ui/CertifyFingerprintActivity.java | 48 ++---- .../ui/CertifyFingerprintFragment.java | 159 +++--------------- .../keychain/ui/DeleteKeyDialogActivity.java | 13 +- .../ui/keyview/UnifiedKeyInfoViewModel.java | 40 +++++ .../keychain/ui/keyview/ViewKeyActivity.java | 31 +--- .../keychain/ui/keyview/ViewKeyFragment.java | 6 +- .../linked/LinkedIdCreateFinalFragment.java | 71 ++++---- .../linked/LinkedIdCreateGithubFragment.java | 132 ++++++--------- .../LinkedIdCreateHttpsStep1Fragment.java | 51 ++---- .../LinkedIdCreateHttpsStep2Fragment.java | 90 ++++------ .../LinkedIdCreateTwitterStep1Fragment.java | 31 +--- .../LinkedIdCreateTwitterStep2Fragment.java | 48 ++---- .../ui/linked/LinkedIdSelectFragment.java | 80 ++------- .../keychain/ui/linked/LinkedIdWizard.java | 104 ++++-------- 15 files changed, 296 insertions(+), 613 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 661d8efab..3eced678e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -140,11 +140,6 @@ public class KeychainContract { .appendPath(PATH_UNIFIED).build(); } - public static Uri buildUnifiedKeyRingUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)) - .appendPath(PATH_UNIFIED).build(); - } - public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) { return CONTENT_URI.buildUpon().appendPath(PATH_FIND) .appendPath(PATH_BY_EMAIL).appendPath(email).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java index 58a4c3793..6c958bf5b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java @@ -17,40 +17,41 @@ package org.sufficientlysecure.keychain.ui; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import timber.log.Timber; public class CertifyFingerprintActivity extends BaseActivity { - - protected Uri mDataUri; + public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mDataUri = getIntent().getData(); - if (mDataUri == null) { - Timber.e("Data missing. Should be uri of key!"); + Bundle extras = getIntent().getExtras(); + if (extras == null || !extras.containsKey(EXTRA_MASTER_KEY_ID)) { + Timber.e("Missing required extra master_key_id!"); finish(); return; } - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); + setFullScreenDialogClose(v -> finish()); - Timber.i("dataUri: " + mDataUri.toString()); + long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID); + UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class); + viewModel.setMasterKeyId(masterKeyId); - startFragment(savedInstanceState, mDataUri); + if (savedInstanceState == null) { + startFragment(); + } } @Override @@ -58,24 +59,9 @@ public class CertifyFingerprintActivity extends BaseActivity { setContentView(R.layout.certify_fingerprint_activity); } - private void startFragment(Bundle savedInstanceState, Uri dataUri) { - // However, if we're being restored from a previous state, - // then we don't need to do anything and should return or else - // we could end up with overlapping fragments. - if (savedInstanceState != null) { - return; - } - - // Create an instance of the fragment - CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri); - - // Add the fragment to the 'fragment_container' FrameLayout - // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! - getSupportFragmentManager().beginTransaction() - .replace(R.id.certify_fingerprint_fragment, frag) - .commitAllowingStateLoss(); - // do it immediately! - getSupportFragmentManager().executePendingTransactions(); + private void startFragment() { + CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(); + getSupportFragmentManager().beginTransaction().replace(R.id.certify_fingerprint_fragment, frag).commit(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java index e024c6672..9abb2ad39 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java @@ -18,79 +18,42 @@ package org.sufficientlysecure.keychain.ui; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; 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.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import timber.log.Timber; -public class CertifyFingerprintFragment extends Fragment implements LoaderManager.LoaderCallbacks { - +public class CertifyFingerprintFragment extends Fragment { static final int REQUEST_CERTIFY = 1; - public static final String ARG_DATA_URI = "uri"; + private TextView vFingerprint; - private TextView mActionYes; - private TextView mFingerprint; - private TextView mIntro; - private TextView mHeader; + private UnifiedKeyInfoViewModel viewModel; - private static final int LOADER_ID_UNIFIED = 0; - - private Uri mDataUri; - - /** - * Creates new instance of this fragment - */ - public static CertifyFingerprintFragment newInstance(Uri dataUri) { - CertifyFingerprintFragment frag = new CertifyFingerprintFragment(); - Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); - - frag.setArguments(args); - - return frag; + public static CertifyFingerprintFragment newInstance() { + return new CertifyFingerprintFragment(); } @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.certify_fingerprint_fragment, viewGroup, false); - TextView actionNo = view.findViewById(R.id.certify_fingerprint_button_no); - mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes); + vFingerprint = view.findViewById(R.id.certify_fingerprint_fingerprint); - mFingerprint = view.findViewById(R.id.certify_fingerprint_fingerprint); - mIntro = view.findViewById(R.id.certify_fingerprint_intro); - mHeader = view.findViewById(R.id.certify_fingerprint_fingerprint_header); - - actionNo.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().finish(); - } - }); - mActionYes.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - certify(mDataUri); - } - }); + view.findViewById(R.id.certify_fingerprint_button_no).setOnClickListener(v -> requireActivity().finish()); + view.findViewById(R.id.certify_fingerprint_button_yes).setOnClickListener(v -> startCertifyActivity()); return view; } @@ -99,103 +62,35 @@ public class CertifyFingerprintFragment extends Fragment implements LoaderManage public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); - getActivity().finish(); + viewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class); + viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); + } + + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { return; } - loadData(dataUri); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); + vFingerprint.setText(KeyFormattingUtils.formatFingerprint(fingerprint)); } - private void loadData(Uri dataUri) { - mDataUri = dataUri; - - Timber.i("dataUri: " + mDataUri.toString()); - - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - } - - static final String[] UNIFIED_PROJECTION = new String[]{ - KeyRings._ID, KeyRings.FINGERPRINT, - - }; - static final int INDEX_UNIFIED_FINGERPRINT = 1; - - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); - } - - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - /* TODO better error handling? May cause problems when a key is deleted, - * because the notification triggers faster than the activity closes. - */ - // Avoid NullPointerExceptions... - if (data.getCount() == 0) { - return; - } - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - if (data.moveToFirst()) { - byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); - - displayHexConfirm(fingerprintBlob); - - break; - } - } - - } - } - - private void displayHexConfirm(byte[] fingerprintBlob) { - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); - mFingerprint.setText(KeyFormattingUtils.formatFingerprint(fingerprint)); - } - - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - } - - private void certify(Uri dataUri) { - long keyId = 0; - try { - keyId = KeyRepository.create(getContext()) - .getCachedPublicKeyRing(dataUri) - .extractOrGetMasterKeyId(); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); - } + private void startCertifyActivity() { Intent certifyIntent = new Intent(getActivity(), CertifyKeyActivity.class); - certifyIntent.putExtras(getActivity().getIntent()); - certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{keyId}); + certifyIntent.putExtras(requireActivity().getIntent()); + certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { viewModel.getMasterKeyId() }); startActivityForResult(certifyIntent, REQUEST_CERTIFY); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - // always just pass this one through if (requestCode == REQUEST_CERTIFY) { - getActivity().setResult(resultCode, data); - getActivity().finish(); + FragmentActivity activity = requireActivity(); + activity.setResult(resultCode, data); + activity.finish(); return; } + super.onActivityResult(requestCode, resultCode, data); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java index d6afada2f..a495deb9a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java @@ -17,8 +17,11 @@ package org.sufficientlysecure.keychain.ui; + +import java.util.Date; +import java.util.HashMap; + import android.app.Activity; -import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; @@ -26,6 +29,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AlertDialog; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -34,12 +38,11 @@ import android.widget.Spinner; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.RevokeResult; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -48,9 +51,6 @@ import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import timber.log.Timber; -import java.util.Date; -import java.util.HashMap; - public class DeleteKeyDialogActivity extends FragmentActivity { public static final String EXTRA_DELETE_MASTER_KEY_IDS = "extra_delete_master_key_ids"; public static final String EXTRA_HAS_SECRET = "extra_has_secret"; @@ -81,6 +81,7 @@ public class DeleteKeyDialogActivity extends FragmentActivity { log.add(OperationResult.LogType.MSG_DEL_ERROR_MULTI_SECRET, 0); returnResult(new DeleteResult(OperationResult.RESULT_ERROR, log, 0, mMasterKeyIds.length)); + return; } if (mMasterKeyIds.length == 1 && mHasSecret) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java new file mode 100644 index 000000000..de07b3832 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java @@ -0,0 +1,40 @@ +package org.sufficientlysecure.keychain.ui.keyview; + + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.content.Context; + +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; + + +public class UnifiedKeyInfoViewModel extends ViewModel { + private Long masterKeyId; + private LiveData unifiedKeyInfoLiveData; + + public void setMasterKeyId(long masterKeyId) { + if (this.masterKeyId != null) { + throw new IllegalStateException("cannot change masterKeyId once set!"); + } + this.masterKeyId = masterKeyId; + } + + public long getMasterKeyId() { + return masterKeyId; + } + + public LiveData getUnifiedKeyInfoLiveData(Context context) { + if (masterKeyId == null) { + throw new IllegalStateException("masterKeyId must be set to retrieve this!"); + } + if (unifiedKeyInfoLiveData == null) { + KeyRepository keyRepository = KeyRepository.create(context); + unifiedKeyInfoLiveData = new GenericLiveData<>(context, KeyRings.buildGenericKeyRingUri(masterKeyId), + () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); + } + return unifiedKeyInfoLiveData; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 2eafd4806..895c273b5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -28,8 +28,6 @@ import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; @@ -70,7 +68,6 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; @@ -269,7 +266,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements qrCodeLayout.setOnClickListener(v -> showQrCodeDialog()); - ViewKeyViewModel viewModel = ViewModelProviders.of(this).get(ViewKeyViewModel.class); + UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class); viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L)); if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) { @@ -296,30 +293,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } } - public static class ViewKeyViewModel extends ViewModel { - private Long masterKeyId; - private LiveData unifiedKeyInfoLiveData; - - void setMasterKeyId(long masterKeyId) { - if (this.masterKeyId != null) { - throw new IllegalStateException("cannot change masterKeyId once set!"); - } - this.masterKeyId = masterKeyId; - } - - LiveData getUnifiedKeyInfoLiveData(Context context) { - if (masterKeyId == null) { - throw new IllegalStateException("masterKeyId must be set to retrieve this!"); - } - if (unifiedKeyInfoLiveData == null) { - KeyRepository keyRepository = KeyRepository.create(context); - unifiedKeyInfoLiveData = new GenericLiveData<>(context, KeyRings.buildGenericKeyRingUri(masterKeyId), - () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); - } - return unifiedKeyInfoLiveData; - } - } - @Override protected void initLayout() { setContentView(R.layout.view_key_activity); @@ -467,7 +440,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void certifyFingerprint() { Intent intent = new Intent(this, CertifyFingerprintActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + intent.putExtra(CertifyFingerprintActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivityForResult(intent, REQUEST_CERTIFY); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index 2a7d83ad8..d81d0d7ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -46,11 +46,9 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; -import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity.ViewKeyViewModel; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo; @@ -119,7 +117,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener Context context = requireContext(); - ViewKeyViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyViewModel.class); + UnifiedKeyInfoViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class); LiveData unifiedKeyInfoLiveData = viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext()); unifiedKeyInfoLiveData.observe(this, this::onLoadUnifiedKeyInfo); @@ -277,7 +275,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener private void addLinkedIdentity() { Intent intent = new Intent(requireContext(), LinkedIdWizard.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + intent.putExtra(LinkedIdWizard.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivity(intent); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java index ee24386cb..55552ae03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.ui.linked; + +import android.arch.lifecycle.ViewModelProviders; import android.graphics.PorterDuff; import android.os.AsyncTask; import android.os.Bundle; @@ -25,7 +27,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; @@ -34,6 +35,7 @@ import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.LinkedTokenResource; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -41,68 +43,57 @@ import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import org.sufficientlysecure.keychain.ui.util.Notify; public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment { - - protected LinkedIdWizard mLinkedIdWizard; - private ImageView mVerifyImage; private TextView mVerifyStatus; private ViewAnimator mVerifyAnimator; + private long masterKeyId; + byte[] fingerprint; + // This is a resource, set AFTER it has been verified LinkedTokenResource mVerifiedResource = null; private ViewAnimator mVerifyButtonAnimator; - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mLinkedIdWizard = (LinkedIdWizard) getActivity(); - } - protected abstract View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); - @Override @NonNull - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class); + viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); + } + + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + this.masterKeyId = unifiedKeyInfo.master_key_id(); + this.fingerprint = unifiedKeyInfo.fingerprint(); + } + + @NonNull + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = newView(inflater, container, savedInstanceState); View nextButton = view.findViewById(R.id.next_button); if (nextButton != null) { - nextButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - cryptoOperation(); - } - }); + nextButton.setOnClickListener(v -> cryptoOperation()); } - view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); - } - }); + view.findViewById(R.id.back_button).setOnClickListener( + v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT)); mVerifyAnimator = view.findViewById(R.id.verify_progress); mVerifyImage = view.findViewById(R.id.verify_image); mVerifyStatus = view.findViewById(R.id.verify_status); mVerifyButtonAnimator = view.findViewById(R.id.verify_buttons); - view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - proofVerify(); - } - }); + view.findViewById(R.id.button_verify).setOnClickListener(v -> proofVerify()); - view.findViewById(R.id.button_retry).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - proofVerify(); - } - }); + view.findViewById(R.id.button_retry).setOnClickListener(v -> proofVerify()); setVerifyProgress(false, null); mVerifyStatus.setText(R.string.linked_verify_pending); @@ -154,7 +145,7 @@ public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragmen return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log); } - LinkedVerifyResult result = resource.verify(getActivity(), mLinkedIdWizard.mFingerprint); + LinkedVerifyResult result = resource.verify(getActivity(), fingerprint); // ux flow: this operation should take at last a second timer = System.currentTimeMillis() -timer; @@ -211,7 +202,7 @@ public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragmen @Override public Parcelable createOperationInput() { SaveKeyringParcel.Builder builder= - SaveKeyringParcel.buildChangeKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint); + SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, fingerprint); WrappedUserAttribute ua = LinkedAttribute.fromResource(mVerifiedResource).toUserAttribute(); builder.addUserAttribute(ua); return builder.build(); @@ -219,7 +210,7 @@ public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragmen @Override public void onCryptoOperationSuccess(OperationResult result) { - getActivity().finish(); + requireActivity().finish(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java index bc6c79dae..81594bcb4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateGithubFragment.java @@ -33,13 +33,11 @@ import java.util.Random; import android.app.Activity; import android.app.Dialog; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -47,11 +45,9 @@ import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityOptionsCompat; -import android.support.v4.app.FragmentActivity; import android.util.Base64; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.webkit.CookieManager; import android.webkit.WebView; @@ -69,10 +65,12 @@ import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.resources.GithubResource; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -109,7 +107,7 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment { + LinkedIdWizard activity = (LinkedIdWizard) requireActivity(); + activity.loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); }); - view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - step1GetOAuthCode(); - // for animation testing - // onCryptoOperationSuccess(null); - } + view.findViewById(R.id.button_send).setOnClickListener(v -> { + step1GetOAuthCode(); + // for animation testing + // onCryptoOperationSuccess(null); }); return view; @@ -152,34 +141,29 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment oAuthRequest("github.com/login/oauth/authorize", BuildConfig.GITHUB_CLIENT_ID, "gist"), 300); } private void showRetryForOAuth() { - - mRetryButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - v.setOnClickListener(null); - step1GetOAuthCode(); - } + mRetryButton.setOnClickListener(v -> { + v.setOnClickListener(null); + step1GetOAuthCode(); }); mButtonContainer.setDisplayedChild(3); @@ -402,14 +386,11 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment { + WrappedUserAttribute ua = LinkedAttribute.fromResource(resource).toUserAttribute(); + mSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(mMasterKeyId, mFingerprint); + mSkpBuilder.addUserAttribute(ua); + cryptoOperation(); }, 250); } @@ -429,31 +410,28 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment { + Activity activity = requireActivity(); + Intent intent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), mMasterKeyId); + // intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - intent.putExtra(ViewKeyActivity.EXTRA_LINKED_TRANSITION, true); - View linkedItem = mButtonContainer.getChildAt(2); + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + intent.putExtra(ViewKeyActivity.EXTRA_LINKED_TRANSITION, true); + View linkedItem = mButtonContainer.getChildAt(2); - Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation( - activity, linkedItem, linkedItem.getTransitionName()).toBundle(); - activity.startActivity(intent, options); - mFinishOnStop = true; - } else { - activity.startActivity(intent); - activity.finish(); - } + Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation( + activity, linkedItem, linkedItem.getTransitionName()).toBundle(); + activity.startActivity(intent, options); + mFinishOnStop = true; + } else { + activity.startActivity(intent); + activity.finish(); } }, 1000); } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); // cookies are automatically saved, we don't want that @@ -463,7 +441,7 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment { + v.setOnClickListener(null); + mButtonContainer.setDisplayedChild(1); + setState(State.LID_PROCESS); + cryptoOperation(); }); mButtonContainer.setDisplayedChild(3); setState(State.LID_ERROR); @@ -573,12 +548,7 @@ public class LinkedIdCreateGithubFragment extends CryptoOperationFragment step1GetOAuthToken()); auth_dialog.show(); web.loadUrl("https://" + hostAndPath + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java index 8a1fd1cc1..4ec67e948 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep1Fragment.java @@ -17,24 +17,22 @@ package org.sufficientlysecure.keychain.ui.linked; + import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.text.Editable; import android.text.TextWatcher; import android.util.Patterns; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.EditText; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; + public class LinkedIdCreateHttpsStep1Fragment extends Fragment { - - LinkedIdWizard mLinkedIdWizard; - EditText mEditUri; public static LinkedIdCreateHttpsStep1Fragment newInstance() { @@ -47,44 +45,23 @@ public class LinkedIdCreateHttpsStep1Fragment extends Fragment { } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mLinkedIdWizard = (LinkedIdWizard) getActivity(); - - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.linked_create_https_fragment_step1, container, false); - view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - - String uri = "https://" + mEditUri.getText(); - - if (!checkUri(uri)) { - return; - } - - String proofText = GenericHttpsResource.generateText(getActivity(), - mLinkedIdWizard.mFingerprint); - - LinkedIdCreateHttpsStep2Fragment frag = - LinkedIdCreateHttpsStep2Fragment.newInstance(uri, proofText); - - mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + view.findViewById(R.id.next_button).setOnClickListener(v -> { + String uri = "https://" + mEditUri.getText(); + if (!checkUri(uri)) { + return; } + + LinkedIdCreateHttpsStep2Fragment frag = LinkedIdCreateHttpsStep2Fragment.newInstance(uri); + + ((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); }); - view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); - } - }); + view.findViewById(R.id.back_button).setOnClickListener( + v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT)); mEditUri = view.findViewById(R.id.linked_create_https_uri); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java index 42e6ebd44..e8b350058 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java @@ -17,47 +17,46 @@ package org.sufficientlysecure.keychain.ui.linked; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.EditText; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.FileHelper; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; -public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment { +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.FileHelper; +import timber.log.Timber; + +public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment { private static final int REQUEST_CODE_OUTPUT = 0x00007007; - public static final String ARG_URI = "uri", ARG_TEXT = "text"; + public static final String ARG_URI = "uri"; EditText mEditUri; URI mResourceUri; String mResourceString; - public static LinkedIdCreateHttpsStep2Fragment newInstance - (String uri, String proofText) { + public static LinkedIdCreateHttpsStep2Fragment newInstance(String uri) { LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment(); Bundle args = new Bundle(); args.putString(ARG_URI, uri); - args.putString(ARG_TEXT, proofText); frag.setArguments(args); return frag; @@ -75,47 +74,33 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen try { mResourceUri = new URI(getArguments().getString(ARG_URI)); } catch (URISyntaxException e) { - e.printStackTrace(); - getActivity().finish(); + Timber.e(e); + requireActivity().finish(); } - mResourceString = getArguments().getString(ARG_TEXT); - - } - - protected View newView(LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false); + mResourceString = GenericHttpsResource.generateText(requireActivity(), fingerprint); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false); + } + + @NonNull + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); - if (view != null) { + view.findViewById(R.id.button_send).setOnClickListener(v -> proofSend()); + view.findViewById(R.id.button_save).setOnClickListener(v -> proofSave()); - view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - proofSend(); - } - }); - - view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - proofSave(); - } - }); - - mEditUri = view.findViewById(R.id.linked_create_https_uri); - mEditUri.setText(mResourceUri.toString()); - } + mEditUri = view.findViewById(R.id.linked_create_https_uri); + mEditUri.setText(mResourceUri.toString()); return view; } - private void proofSend () { + private void proofSend() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString); @@ -123,7 +108,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen startActivity(sendIntent); } - private void proofSave () { + private void proofSave() { String state = Environment.getExternalStorageState(); if (!Environment.MEDIA_MOUNTED.equals(state)) { Notify.create(getActivity(), "External storage not available!", Style.ERROR).show(); @@ -138,8 +123,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen private void saveFile(Uri uri) { try { - PrintWriter out = - new PrintWriter(getActivity().getContentResolver().openOutputStream(uri)); + PrintWriter out = new PrintWriter(requireActivity().getContentResolver().openOutputStream(uri)); out.print(mResourceString); if (out.checkError()) { Notify.create(getActivity(), "Error writing file!", Style.ERROR).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java index e93a2c7fd..41d3f2adb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep1Fragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.linked; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -30,30 +31,14 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.util.Notify; public class LinkedIdCreateTwitterStep1Fragment extends Fragment { - - LinkedIdWizard mLinkedIdWizard; - EditText mEditHandle; public static LinkedIdCreateTwitterStep1Fragment newInstance() { - LinkedIdCreateTwitterStep1Fragment frag = new LinkedIdCreateTwitterStep1Fragment(); - - Bundle args = new Bundle(); - frag.setArguments(args); - - return frag; + return new LinkedIdCreateTwitterStep1Fragment(); } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mLinkedIdWizard = (LinkedIdWizard) getActivity(); - - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false); view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() { @@ -96,19 +81,15 @@ public class LinkedIdCreateTwitterStep1Fragment extends Fragment { LinkedIdCreateTwitterStep2Fragment frag = LinkedIdCreateTwitterStep2Fragment.newInstance(handle); - mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + ((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); } }.execute(); } }); - view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT); - } - }); + view.findViewById(R.id.back_button).setOnClickListener( + v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT)); mEditHandle = view.findViewById(R.id.linked_create_twitter_handle); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java index 9bd00c0db..7e5ca0051 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateTwitterStep2Fragment.java @@ -17,20 +17,23 @@ package org.sufficientlysecure.keychain.ui.linked; + import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.Html; +import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.linked.LinkedTokenResource; import org.sufficientlysecure.keychain.linked.resources.TwitterResource; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragment { @@ -39,9 +42,7 @@ public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragm String mResourceHandle; String mResourceString; - public static LinkedIdCreateTwitterStep2Fragment newInstance - (String handle) { - + public static LinkedIdCreateTwitterStep2Fragment newInstance(String handle) { LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment(); Bundle args = new Bundle(); @@ -52,39 +53,23 @@ public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragm } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mResourceString = - TwitterResource.generate(mLinkedIdWizard.mFingerprint); + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mResourceString = TwitterResource.generate(fingerprint); mResourceHandle = getArguments().getString(ARG_HANDLE); - } + @NonNull @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); - if (view != null) { - view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - proofSend(); - } - }); + view.findViewById(R.id.button_send).setOnClickListener(v -> proofSend()); + view.findViewById(R.id.button_share).setOnClickListener(v -> proofShare()); - view.findViewById(R.id.button_share).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - proofShare(); - } - }); - - ((TextView) view.findViewById(R.id.linked_tweet_published)).setText( - Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle)) - ); - } + Spanned tweetText = Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle)); + ((TextView) view.findViewById(R.id.linked_tweet_published)).setText(tweetText); return view; } @@ -109,13 +94,12 @@ public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragm } private void proofSend() { - Uri.Builder builder = Uri.parse("https://twitter.com/intent/tweet").buildUpon(); builder.appendQueryParameter("text", mResourceString); Uri uri = builder.build(); Intent intent = new Intent(Intent.ACTION_VIEW, uri); - getActivity().startActivity(intent); + startActivity(intent); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java index b7c00552e..cd8262aaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdSelectFragment.java @@ -17,7 +17,9 @@ package org.sufficientlysecure.keychain.ui.linked; + import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -25,81 +27,33 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; + public class LinkedIdSelectFragment extends Fragment { - - LinkedIdWizard mLinkedIdWizard; - - /** - * Creates new instance of this fragment - */ public static LinkedIdSelectFragment newInstance() { - LinkedIdSelectFragment frag = new LinkedIdSelectFragment(); - - Bundle args = new Bundle(); - frag.setArguments(args); - - return frag; + return new LinkedIdSelectFragment(); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.linked_select_fragment, container, false); - view.findViewById(R.id.linked_create_https_button) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - LinkedIdCreateHttpsStep1Fragment frag = - LinkedIdCreateHttpsStep1Fragment.newInstance(); + view.findViewById(R.id.linked_create_https_button).setOnClickListener(v -> { + LinkedIdCreateHttpsStep1Fragment frag = LinkedIdCreateHttpsStep1Fragment.newInstance(); + ((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + }); - mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); - } - }); + view.findViewById(R.id.linked_create_twitter_button).setOnClickListener(v -> { + LinkedIdCreateTwitterStep1Fragment frag = LinkedIdCreateTwitterStep1Fragment.newInstance(); + ((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + }); - /* - view.findViewById(R.id.linked_create_dns_button) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - LinkedIdCreateDnsStep1Fragment frag = - LinkedIdCreateDnsStep1Fragment.newInstance(); - - mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); - } - }); - */ - - view.findViewById(R.id.linked_create_twitter_button) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - LinkedIdCreateTwitterStep1Fragment frag = - LinkedIdCreateTwitterStep1Fragment.newInstance(); - - mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); - } - }); - - view.findViewById(R.id.linked_create_github_button) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - LinkedIdCreateGithubFragment frag = - LinkedIdCreateGithubFragment.newInstance(); - - mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); - } - }); + view.findViewById(R.id.linked_create_github_button).setOnClickListener(v -> { + LinkedIdCreateGithubFragment frag = LinkedIdCreateGithubFragment.newInstance(); + ((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT); + }); return view; } - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mLinkedIdWizard = (LinkedIdWizard) getActivity(); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java index 28d9a22c5..bea6df04b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdWizard.java @@ -18,64 +18,60 @@ package org.sufficientlysecure.keychain.ui.linked; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.NavUtils; -import android.support.v4.app.TaskStackBuilder; -import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import timber.log.Timber; public class LinkedIdWizard extends BaseActivity { + public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; public static final int FRAG_ACTION_START = 0; public static final int FRAG_ACTION_TO_RIGHT = 1; public static final int FRAG_ACTION_TO_LEFT = 2; - long mMasterKeyId; - byte[] mFingerprint; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(getString(R.string.title_linked_id_create)); - try { - Uri uri = getIntent().getData(); - uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(uri); - CachedPublicKeyRing ring = KeyRepository.create(this).getCachedPublicKeyRing(uri); - if (!ring.hasAnySecret()) { - Timber.e("Linked Identities can only be added to secret keys!"); - finish(); - return; - } - - mMasterKeyId = ring.extractOrGetMasterKeyId(); - mFingerprint = ring.getFingerprint(); - } catch (PgpKeyNotFoundException e) { - Timber.e("Invalid uri given, key does not exist!"); + Bundle extras = getIntent().getExtras(); + if (extras == null || !extras.containsKey(EXTRA_MASTER_KEY_ID)) { + Timber.e("Missing required extra master_key_id!"); finish(); return; } + long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID); + UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class); + viewModel.setMasterKeyId(masterKeyId); + viewModel.getUnifiedKeyInfoLiveData(this).observe(this, this::onLoadUnifiedKeyInfo); + + hideKeyboard(); + // pass extras into fragment - LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance(); - loadFragment(null, frag, FRAG_ACTION_START); + if (savedInstanceState == null) { + LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance(); + loadFragment(frag, FRAG_ACTION_START); + } + } + + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (!unifiedKeyInfo.has_any_secret()) { + Timber.e("Linked Identities can only be added to secret keys!"); + finish(); + } } @Override @@ -83,16 +79,7 @@ public class LinkedIdWizard extends BaseActivity { setContentView(R.layout.create_key_activity); } - public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) { - // However, if we're being restored from a previous state, - // then we don't need to do anything and should return or else - // we could end up with overlapping fragments. - if (savedInstanceState != null) { - return; - } - - hideKeyboard(); - + public void loadFragment(Fragment fragment, int action) { // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); @@ -115,50 +102,17 @@ public class LinkedIdWizard extends BaseActivity { break; } - // do it immediately! getSupportFragmentManager().executePendingTransactions(); } private void hideKeyboard() { - InputMethodManager inputManager = (InputMethodManager) - getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - // check if no view has focus View v = getCurrentFocus(); - if (v == null) + if (v == null || inputManager == null) { return; + } inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); } - - @Override - public void onBackPressed() { - if (!getFragmentManager().popBackStackImmediate()) { - navigateBack(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - // Respond to the action bar's Up/Home button - case android.R.id.home: - navigateBack(); - return true; - } - return super.onOptionsItemSelected(item); - } - - private void navigateBack() { - Intent upIntent = NavUtils.getParentActivityIntent(this); - upIntent.setData(KeyRings.buildGenericKeyRingUri(mMasterKeyId)); - // This activity is NOT part of this app's task, so create a new task - // when navigating up, with a synthesized back stack. - TaskStackBuilder.create(this) - // Add all of this activity's parents to the back stack - .addNextIntentWithParentStack(upIntent) - // Navigate up to the closest parent - .startActivities(); - } - } From 5d8bc8fc50270d92fad3eab9d8ab9ec8b573107e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 12:41:45 +0200 Subject: [PATCH 036/124] remove unused SelectIdentityKeyListFragment --- .../ui/SelectIdentityKeyListFragment.java | 167 ------------------ 1 file changed, 167 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectIdentityKeyListFragment.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectIdentityKeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectIdentityKeyListFragment.java deleted file mode 100644 index 3b6d033c2..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectIdentityKeyListFragment.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui; - - -import android.app.Activity; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v7.widget.LinearLayoutManager; - -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.remote.ui.adapter.SelectIdentityKeyAdapter; -import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; -import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; - - -public class SelectIdentityKeyListFragment extends RecyclerFragment - implements SelectIdentityKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks { - private static final String ARG_API_IDENTITY = "api_identity"; - private String apiIdentity; - private boolean listAllKeys; - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.getString(ARG_API_IDENTITY, apiIdentity); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - if (savedInstanceState != null && apiIdentity == null) { - apiIdentity = getArguments().getString(ARG_API_IDENTITY); - } - - SelectIdentityKeyAdapter adapter = new SelectIdentityKeyAdapter(getContext(), null); - adapter.setListener(this); - - setAdapter(adapter); - LinearLayoutManager layoutManager = new LinearLayoutManager(getContext()); - DividerItemDecoration dividerItemDecoration = - new DividerItemDecoration(getContext(), layoutManager.getOrientation(), true); - setLayoutManager(layoutManager); - getRecyclerView().addItemDecoration(dividerItemDecoration); - - // Start out with a progress indicator. - hideList(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_EXPIRED, - KeyRings.IS_REVOKED, - KeyRings.HAS_ENCRYPT, - KeyRings.VERIFIED, - KeyRings.HAS_ANY_SECRET, - KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.CREATION, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT, - }; - - String selection = KeyRings.HAS_ANY_SECRET + " != 0"; - Uri baseUri = listAllKeys ? KeyRings.buildUnifiedKeyRingsUri() : - KeyRings.buildUnifiedKeyRingsFindByEmailUri(apiIdentity); - - String orderBy = KeyRings.USER_ID + " ASC"; - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, projection, selection, null, orderBy); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - getAdapter().swapCursor(CursorAdapter.KeyCursor.wrap(data)); - - // The list should now be shown. - if (isResumed()) { - showList(true); - } else { - showList(false); - } - - boolean isEmpty = data.getCount() == 0; - getKeySelectFragmentListener().onChangeListEmptyStatus(isEmpty); - } - - @Override - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - getAdapter().swapCursor(null); - } - - @Override - public void onDestroy() { - getAdapter().setListener(null); - super.onDestroy(); - } - - @Override - public void onSelectKeyItemClicked(long masterKeyId) { - getKeySelectFragmentListener().onKeySelected(masterKeyId); - } - - SelectIdentityKeyFragmentListener getKeySelectFragmentListener() { - Activity activity = getActivity(); - if (activity == null) { - return null; - } - - if (!(activity instanceof SelectIdentityKeyFragmentListener)) { - throw new IllegalStateException("SelectIdentityKeyListFragment must be attached to KeySelectFragmentListener!"); - } - - return (SelectIdentityKeyFragmentListener) activity; - } - - public void setApiIdentity(String apiIdentity) { - this.apiIdentity = apiIdentity; - } - - public void setListAllKeys(boolean listAllKeys) { - this.listAllKeys = listAllKeys; - getLoaderManager().restartLoader(0, null, this); - } - - public interface SelectIdentityKeyFragmentListener { - void onKeySelected(Long masterKeyId); - void onChangeListEmptyStatus(boolean isEmpty); - } - -} From 0793300b73f48098ae452a9d7f236cfe10cd4c78 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 13:04:07 +0200 Subject: [PATCH 037/124] use LiveData in QrCodeViewActivity --- .../keychain/ui/QrCodeViewActivity.java | 98 +++++++------------ .../keychain/ui/ViewKeyAdvShareFragment.java | 2 +- .../keychain/ui/keyview/ViewKeyActivity.java | 2 +- 3 files changed, 40 insertions(+), 62 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java index 5e94bace3..80c757563 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java @@ -17,95 +17,73 @@ package org.sufficientlysecure.keychain.ui; + +import android.arch.lifecycle.ViewModelProviders; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v7.widget.CardView; -import android.view.View; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; -import timber.log.Timber; public class QrCodeViewActivity extends BaseActivity { + public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; - private ImageView mQrCode; - private CardView mQrCodeLayout; + private ImageView qrCodeImageView; + private Bitmap qrCode; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Inflate a "Done" custom action bar - setFullScreenDialogClose( - new View.OnClickListener() { - @Override - public void onClick(View v) { - // "Done" - ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); - } - } - ); + setFullScreenDialogClose(v -> ActivityCompat.finishAfterTransition(QrCodeViewActivity.this)); - Uri dataUri = getIntent().getData(); - if (dataUri == null) { - Timber.e("Data missing. Should be Uri of key!"); + qrCodeImageView = findViewById(R.id.qr_code_image); + CardView mQrCodeLayout = findViewById(R.id.qr_code_image_layout); + + mQrCodeLayout.setOnClickListener(v -> ActivityCompat.finishAfterTransition(QrCodeViewActivity.this)); + + if (!getIntent().hasExtra(EXTRA_MASTER_KEY_ID)) { + throw new IllegalArgumentException("Missing required extra master_key_id"); + } + + UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class); + viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L)); + viewModel.getUnifiedKeyInfoLiveData(getApplicationContext()).observe(this, this::onLoadUnifiedKeyInfo); + + qrCodeImageView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + if (qrCode != null) { + Bitmap scaled = Bitmap.createScaledBitmap(qrCode, qrCodeImageView.getWidth(), qrCodeImageView.getWidth(), false); + qrCodeImageView.setImageBitmap(scaled); + } + }); + } + + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { + Notify.create(this, R.string.error_key_not_found, Style.ERROR).show(); ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); return; } - mQrCode = findViewById(R.id.qr_code_image); - mQrCodeLayout = findViewById(R.id.qr_code_image_layout); + Uri uri = new Uri.Builder() + .scheme(Constants.FINGERPRINT_SCHEME) + .opaquePart(KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint())) + .build(); + qrCode = QrCodeUtils.getQRCodeBitmap(uri, 0); - mQrCodeLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); - } - }); - - KeyRepository keyRepository = KeyRepository.create(this); - try { - byte[] blob = keyRepository.getCachedPublicKeyRing(dataUri).getFingerprint(); - if (blob == null) { - Timber.e("key not found!"); - Notify.create(this, R.string.error_key_not_found, Style.ERROR).show(); - ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); - } - - Uri uri = new Uri.Builder() - .scheme(Constants.FINGERPRINT_SCHEME) - .opaquePart(KeyFormattingUtils.convertFingerprintToHex(blob)) - .build(); - // create a minimal size qr code, we can keep this in ram no problem - final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(uri, 0); - - mQrCode.getViewTreeObserver().addOnGlobalLayoutListener( - new OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - // create actual bitmap in display dimensions - Bitmap scaled = Bitmap.createScaledBitmap(qrCode, - mQrCode.getWidth(), mQrCode.getWidth(), false); - mQrCode.setImageBitmap(scaled); - } - }); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "key not found!"); - Notify.create(this, R.string.error_key_not_found, Style.ERROR).show(); - ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); - } + qrCodeImageView.requestLayout(); } @Override 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 3815af45d..9ab1970c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -282,7 +282,7 @@ public class ViewKeyAdvShareFragment extends Fragment { opts = options.toBundle(); } - qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + qrCodeIntent.putExtra(QrCodeViewActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); ActivityCompat.startActivity(requireActivity(), qrCodeIntent, opts); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 895c273b5..fcce48c90 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -464,7 +464,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements opts = options.toBundle(); } - qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); + qrCodeIntent.putExtra(QrCodeViewActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); ActivityCompat.startActivity(this, qrCodeIntent, opts); } From e6fe35346921e820245880d5b3c7d24add6c5f53 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 13:07:44 +0200 Subject: [PATCH 038/124] minor cleanup in UploadKeyActivity --- .../keychain/ui/UploadKeyActivity.java | 18 ++++++------------ .../keychain/ui/ViewKeyAdvShareFragment.java | 4 +--- 2 files changed, 7 insertions(+), 15 deletions(-) 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 ba1c7f043..b3c8f6397 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import android.content.Intent; import android.os.Bundle; import android.view.View; -import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Spinner; @@ -40,7 +39,8 @@ import org.sufficientlysecure.keychain.util.Preferences; */ public class UploadKeyActivity extends BaseActivity implements CryptoOperationHelper.Callback { - private View mUploadButton; + public static final String EXTRA_KEY_IDS = "extra_key_ids"; + private Spinner mKeyServerSpinner; // CryptoOperationHelper.Callback vars @@ -51,7 +51,7 @@ public class UploadKeyActivity extends BaseActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mUploadButton = findViewById(R.id.upload_key_action_upload); + View uploadButton = findViewById(R.id.upload_key_action_upload); mKeyServerSpinner = findViewById(R.id.upload_key_keyserver); MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment) @@ -66,15 +66,10 @@ public class UploadKeyActivity extends BaseActivity if (adapter.getCount() > 0) { mKeyServerSpinner.setSelection(0); } else { - mUploadButton.setEnabled(false); + uploadButton.setEnabled(false); } - mUploadButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - uploadKey(); - } - }); + uploadButton.setOnClickListener(v -> uploadKey()); } private String[] getKeyserversArray() { @@ -113,8 +108,7 @@ public class UploadKeyActivity extends BaseActivity @Override public UploadKeyringParcel createOperationInput() { - long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS); - + long[] masterKeyIds = getIntent().getLongArrayExtra(EXTRA_KEY_IDS); return UploadKeyringParcel.createWithKeyId(mKeyserver, masterKeyIds[0]); } 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 9ab1970c1..89b6b2cfd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -60,7 +60,6 @@ import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; @@ -335,8 +334,7 @@ public class ViewKeyAdvShareFragment extends Fragment { private void uploadToKeyserver() { Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class); - uploadIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); - uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{ unifiedKeyInfo.master_key_id() }); + uploadIntent.putExtra(UploadKeyActivity.EXTRA_KEY_IDS, new long[] { unifiedKeyInfo.master_key_id() }); startActivityForResult(uploadIntent, 0); } From fa127add45f95149e8c8d9f47c85abfa065976f8 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 24 Jun 2018 13:15:28 +0200 Subject: [PATCH 039/124] use LiveData in ViewKeyKeybaseFragment --- .../keychain/ui/ViewKeyKeybaseFragment.java | 110 ++++-------------- .../keychain/ui/keyview/ViewKeyActivity.java | 7 +- 2 files changed, 22 insertions(+), 95 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java index 3341ee37e..70fb46e70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java @@ -22,17 +22,13 @@ import java.util.ArrayList; import java.util.Hashtable; import java.util.List; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; -import android.database.Cursor; import android.graphics.Typeface; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; 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.text.SpannableStringBuilder; import android.text.Spanned; import android.text.method.LinkMovementMethod; @@ -51,52 +47,37 @@ import com.textuality.keybase.lib.KeybaseQuery; import com.textuality.keybase.lib.Proof; import com.textuality.keybase.lib.User; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; public class ViewKeyKeybaseFragment extends Fragment implements - LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { - - public static final String ARG_MASTER_KEY_ID = "master_key_id"; - private TextView mReportHeader; private TableLayout mProofListing; private LayoutInflater mInflater; private View mProofVerifyHeader; private TextView mProofVerifyDetail; - private static final int LOADER_ID_DATABASE = 1; - - // for retrieving the key we’re working on - private long masterKeyId; - private Proof mProof; // for CryptoOperationHelper,Callback private String mKeybaseProof; private String mKeybaseFingerprint; - private CryptoOperationHelper - mKeybaseOpHelper; + private CryptoOperationHelper mKeybaseOpHelper; /** * Creates new instance of this fragment */ - public static ViewKeyKeybaseFragment newInstance(long masterKeyId) { - ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment(); - Bundle args = new Bundle(); - args.putLong(ARG_MASTER_KEY_ID, masterKeyId); - - frag.setArguments(args); - - return frag; + public static ViewKeyKeybaseFragment newInstance() { + return new ViewKeyKeybaseFragment(); } @Override @@ -120,57 +101,16 @@ public class ViewKeyKeybaseFragment extends Fragment implements public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - masterKeyId = getArguments().getLong(ARG_MASTER_KEY_ID); - if (masterKeyId == 0L) { - throw new IllegalArgumentException(); - } - - // retrieve the key from the database - getLoaderManager().initLoader(LOADER_ID_DATABASE, null, this); + UnifiedKeyInfoViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class); + viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); } - static final String[] TRUST_PROJECTION = new String[]{ - KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, - KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED - }; - static final int INDEX_TRUST_FINGERPRINT = 1; - static final int INDEX_TRUST_IS_REVOKED = 2; - static final int INDEX_TRUST_IS_EXPIRED = 3; - static final int INDEX_UNIFIED_HAS_ANY_SECRET = 4; - static final int INDEX_VERIFIED = 5; - - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_DATABASE: { - Uri baseUri = KeyRings.buildUnifiedKeyRingUri(masterKeyId); - return new CursorLoader(getActivity(), baseUri, TRUST_PROJECTION, null, null, null); - } - // decided to just use an AsyncTask for keybase, but maybe later - default: - return null; - } - } - - public void onLoadFinished(Loader loader, Cursor data) { - /* TODO better error handling? May cause problems when a key is deleted, - * because the notification triggers faster than the activity closes. - */ - // Avoid NullPointerExceptions... - if (data.getCount() == 0) { + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { return; } - - boolean nothingSpecial = true; - - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - if (data.moveToFirst()) { - - final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); - final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); - - startSearch(fingerprint); - } + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); + startSearch(fingerprint); } private void startSearch(final String fingerprint) { @@ -200,19 +140,11 @@ public class ViewKeyKeybaseFragment extends Fragment implements } } - /** - * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. - * We need to make sure we are no longer using it. - */ - public void onLoaderReset(Loader loader) { - // no-op in this case I think - } - class ResultPage { String mHeader; final List mProofs; - public ResultPage(String header, List proofs) { + ResultPage(String header, List proofs) { mHeader = header; mProofs = proofs; } @@ -224,7 +156,7 @@ public class ViewKeyKeybaseFragment extends Fragment implements private class DescribeKey extends AsyncTask { ParcelableProxy mParcelableProxy; - public DescribeKey(ParcelableProxy parcelableProxy) { + DescribeKey(ParcelableProxy parcelableProxy) { mParcelableProxy = parcelableProxy; } @@ -232,8 +164,8 @@ public class ViewKeyKeybaseFragment extends Fragment implements protected ResultPage doInBackground(String... args) { String fingerprint = args[0]; - final ArrayList proofList = new ArrayList(); - final Hashtable> proofs = new Hashtable>(); + final ArrayList proofList = new ArrayList<>(); + final Hashtable> proofs = new Hashtable<>(); try { KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient()); keybaseQuery.setProxy(mParcelableProxy.getProxy()); @@ -285,7 +217,7 @@ public class ViewKeyKeybaseFragment extends Fragment implements return ssb; } - private SpannableStringBuilder appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException { + private void appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException { int startAt = ssb.length(); String handle = proof.getHandle(); ssb.append(handle); @@ -307,7 +239,6 @@ public class ViewKeyKeybaseFragment extends Fragment implements ssb.setSpan(clicker, startAt, startAt + verify.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append("]"); } - return ssb; } @Override @@ -319,7 +250,7 @@ public class ViewKeyKeybaseFragment extends Fragment implements } if (result.mProofs.isEmpty()) { - result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence); + result.mHeader = requireActivity().getString(R.string.key_trust_no_cloud_evidence); } mReportHeader.setVisibility(View.VISIBLE); @@ -374,10 +305,10 @@ public class ViewKeyKeybaseFragment extends Fragment implements } } - private void appendIfOK(Hashtable> table, Integer proofType, Proof proof) throws KeybaseException { + private void appendIfOK(Hashtable> table, Integer proofType, Proof proof) { ArrayList list = table.get(proofType); if (list == null) { - list = new ArrayList(); + list = new ArrayList<>(); table.put(proofType, list); } list.add(proof); @@ -504,7 +435,6 @@ public class ViewKeyKeybaseFragment extends Fragment implements @Override public void onCryptoOperationError(KeybaseVerificationResult result) { - result.createNotify(getActivity()).show(); SpannableStringBuilder ssb = new SpannableStringBuilder(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index fcce48c90..c69f91e26 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -285,11 +285,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements manager.beginTransaction().replace(R.id.view_key_fragment, frag, "view_key_fragment").commit(); if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { - manager = getSupportFragmentManager(); - final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(masterKeyId); - manager.beginTransaction() - .replace(R.id.view_key_keybase_fragment, keybaseFrag) - .commit(); + final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(); + manager.beginTransaction().replace(R.id.view_key_keybase_fragment, keybaseFrag).commit(); } } From 64bde4c6809936e24e64e51d91e96c26e42d2398 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 14:12:22 +0200 Subject: [PATCH 040/124] extract findByUserId and findByEmail usage from KeychainProvider --- .../keychain/livedata/KeyInfoInteractor.java | 145 ------------------ .../keychain/livedata/KeyInfoLiveData.java | 38 ----- .../keychain/model/SubKey.java | 6 +- .../keychain/provider/KeyRepository.java | 10 ++ .../keychain/provider/KeychainContract.java | 12 -- .../keychain/provider/KeychainDatabase.java | 19 ++- .../keychain/provider/KeychainProvider.java | 50 +----- .../remote/ApiPendingIntentFactory.java | 2 +- .../remote/ui/SecurityProblemPresenter.java | 6 +- .../remote/ui/dialog/KeyInfoLoader.java | 76 --------- .../ui/dialog/RemoteDeduplicateActivity.java | 97 +++++++----- .../ui/dialog/RemoteDeduplicatePresenter.java | 63 +++----- ...RemoteSelectAuthenticationKeyActivity.java | 87 ++++++----- ...emoteSelectAuthenticationKeyPresenter.java | 53 ++----- .../ui/dialog/RemoteSelectIdKeyActivity.java | 95 ++++++++---- .../ui/dialog/RemoteSelectIdViewModel.java | 39 ----- .../RemoteSelectIdentityKeyPresenter.java | 74 +++++---- .../org/sufficientlysecure/keychain/Keys.sq | 37 +++-- 18 files changed, 305 insertions(+), 604 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoInteractor.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoLiveData.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyInfoLoader.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdViewModel.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoInteractor.java deleted file mode 100644 index 173e58ac5..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoInteractor.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.livedata; - - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import android.content.ContentResolver; -import android.database.Cursor; -import android.net.Uri; -import android.support.annotation.Nullable; - -import com.google.auto.value.AutoValue; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.util.DatabaseUtil; - - -public class KeyInfoInteractor { - // These are the rows that we will retrieve. - private String[] QUERY_PROJECTION = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.CREATION, - KeyRings.HAS_ENCRYPT, - KeyRings.HAS_AUTHENTICATE_SECRET, - KeyRings.HAS_ANY_SECRET, - KeyRings.VERIFIED, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT, - KeyRings.IS_EXPIRED, - KeyRings.IS_REVOKED, - }; - private static final int INDEX_MASTER_KEY_ID = 1; - private static final int INDEX_CREATION = 2; - private static final int INDEX_HAS_ENCRYPT = 3; - private static final int INDEX_HAS_AUTHENTICATE = 4; - private static final int INDEX_HAS_ANY_SECRET = 5; - private static final int INDEX_VERIFIED = 6; - private static final int INDEX_NAME = 7; - private static final int INDEX_EMAIL = 8; - private static final int INDEX_COMMENT = 9; - - private static final String QUERY_WHERE_ALL = Tables.KEYS + "." + KeyRings.IS_REVOKED + - " = 0 AND " + KeyRings.IS_EXPIRED + " = 0"; - private static final String QUERY_WHERE_SECRET = Tables.KEYS + "." + KeyRings.IS_REVOKED + - " = 0 AND " + KeyRings.IS_EXPIRED + " = 0" + " AND " + KeyRings.HAS_ANY_SECRET + " != 0"; - private static final String QUERY_ORDER = Tables.KEYS + "." + KeyRings.CREATION + " DESC"; - - private final ContentResolver contentResolver; - - - public KeyInfoInteractor(ContentResolver contentResolver) { - this.contentResolver = contentResolver; - } - - public List loadKeyInfo(KeySelector keySelector) { - ArrayList keyInfos = new ArrayList<>(); - Cursor cursor; - - String selection = keySelector.isOnlySecret() ? QUERY_WHERE_SECRET : QUERY_WHERE_ALL; - String additionalSelection = keySelector.getSelection(); - - selection = DatabaseUtil.concatenateWhere(selection, additionalSelection); - cursor = contentResolver.query(keySelector.getKeyRingUri(), QUERY_PROJECTION, selection, null, QUERY_ORDER); - - if (cursor == null) { - return null; - } - - while (cursor.moveToNext()) { - KeyInfo keyInfo = KeyInfo.fromCursor(cursor); - keyInfos.add(keyInfo); - } - - return Collections.unmodifiableList(keyInfos); - } - - @AutoValue - public abstract static class KeyInfo { - public abstract long getMasterKeyId(); - public abstract long getCreationDate(); - public abstract boolean getHasEncrypt(); - public abstract boolean getHasAuthenticate(); - public abstract boolean getHasAnySecret(); - public abstract boolean getIsVerified(); - - @Nullable - public abstract String getName(); - @Nullable - public abstract String getEmail(); - @Nullable - public abstract String getComment(); - - static KeyInfo fromCursor(Cursor cursor) { - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - long creationDate = cursor.getLong(INDEX_CREATION) * 1000L; - boolean hasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0; - boolean hasAuthenticate = cursor.getInt(INDEX_HAS_AUTHENTICATE) != 0; - boolean hasAnySecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; - boolean isVerified = cursor.getInt(INDEX_VERIFIED) == 2; - - String name = cursor.getString(INDEX_NAME); - String email = cursor.getString(INDEX_EMAIL); - String comment = cursor.getString(INDEX_COMMENT); - - return new AutoValue_KeyInfoInteractor_KeyInfo( - masterKeyId, creationDate, hasEncrypt, hasAuthenticate, hasAnySecret, isVerified, name, email, comment); - } - } - - @AutoValue - public abstract static class KeySelector { - public abstract Uri getKeyRingUri(); - @Nullable - public abstract String getSelection(); - public abstract boolean isOnlySecret(); - - public static KeySelector create(Uri keyRingUri, String selection) { - return new AutoValue_KeyInfoInteractor_KeySelector(keyRingUri, selection, false); - } - - public static KeySelector createOnlySecret(Uri keyRingUri, String selection) { - return new AutoValue_KeyInfoInteractor_KeySelector(keyRingUri, selection, true); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoLiveData.java deleted file mode 100644 index 502ab2f20..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/KeyInfoLiveData.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.sufficientlysecure.keychain.livedata; - - -import java.util.List; - -import android.content.ContentResolver; -import android.content.Context; - -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector; -import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; - - -public class KeyInfoLiveData extends AsyncTaskLiveData> { - private final KeyInfoInteractor keyInfoInteractor; - - private KeySelector keySelector; - - public KeyInfoLiveData(Context context, ContentResolver contentResolver) { - super(context, null); - - this.keyInfoInteractor = new KeyInfoInteractor(contentResolver); - } - - public void setKeySelector(KeySelector keySelector) { - this.keySelector = keySelector; - - updateDataInBackground(); - } - - @Override - protected List asyncLoadData() { - if (keySelector == null) { - return null; - } - return keyInfoInteractor.loadKeyInfo(keySelector); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 0f5463810..b834bcdb2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -15,8 +15,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; public abstract class SubKey implements KeysModel { public static final Factory FACTORY = new Factory<>(AutoValue_SubKey::new, CustomColumnAdapters.SECRET_KEY_TYPE_ADAPTER); - public static final SelectAllUnifiedKeyInfoMapper UNIFIED_KEY_INFO_MAPPER = - FACTORY.selectAllUnifiedKeyInfoMapper(AutoValue_SubKey_UnifiedKeyInfo::new); + public static final UnifiedKeyViewMapper UNIFIED_KEY_INFO_MAPPER = + FACTORY.selectUnifiedKeyInfoByMasterKeyIdMapper(AutoValue_SubKey_UnifiedKeyInfo::new); public static Mapper SUBKEY_MAPPER = new Mapper<>(FACTORY); public static RowMapper SKT_MAPPER = FACTORY.selectSecretKeyTypeMapper(); @@ -25,7 +25,7 @@ public abstract class SubKey implements KeysModel { } @AutoValue - public static abstract class UnifiedKeyInfo implements SelectAllUnifiedKeyInfoModel { + public static abstract class UnifiedKeyInfo implements KeysModel.UnifiedKeyViewModel { private List autocryptPackageNames; public boolean is_expired() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index a88937e11..cab949179 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -264,11 +264,21 @@ public class KeyRepository extends AbstractDao { } } + public List getUnifiedKeyInfosByMailAddress(String mailAddress) { + SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%'); + return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + } + public List getAllUnifiedKeyInfo() { SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo(); return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); } + public List getAllUnifiedKeyInfoWithSecret() { + SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret(); + return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + } + public List getUserIds(long... masterKeyIds) { SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds); return mapAllRows(query, UserPacket.USER_ID_MAPPER::map); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 3eced678e..7085ec839 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -100,9 +100,7 @@ public class KeychainContract { public static final String PATH_UNIFIED = "unified"; public static final String PATH_FIND = "find"; - public static final String PATH_BY_EMAIL = "email"; public static final String PATH_BY_SUBKEY = "subkey"; - public static final String PATH_BY_USER_ID = "user_id"; public static final String PATH_PUBLIC = "public"; public static final String PATH_USER_IDS = "user_ids"; @@ -140,16 +138,6 @@ public class KeychainContract { .appendPath(PATH_UNIFIED).build(); } - public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) { - return CONTENT_URI.buildUpon().appendPath(PATH_FIND) - .appendPath(PATH_BY_EMAIL).appendPath(email).build(); - } - - public static Uri buildUnifiedKeyRingsFindByUserIdUri(String query) { - return CONTENT_URI.buildUpon().appendPath(PATH_FIND) - .appendPath(PATH_BY_USER_ID).appendPath(query).build(); - } - public static Uri buildUnifiedKeyRingsFindBySubkeyUri(long subkey) { return CONTENT_URI.buildUpon().appendPath(PATH_FIND) .appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 2363bffe9..62e643b7d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeyMetadataModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel; import org.sufficientlysecure.keychain.KeySignaturesModel; +import org.sufficientlysecure.keychain.KeysModel; import org.sufficientlysecure.keychain.UserPacketsModel; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.model.Certification; @@ -65,7 +66,7 @@ import timber.log.Timber; */ public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; - private static final int DATABASE_VERSION = 28; + private static final int DATABASE_VERSION = 29; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; private Context context; @@ -205,6 +206,7 @@ public class KeychainDatabase { db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(AutocryptPeersModel.CREATE_TABLE); db.execSQL(ApiAppsModel.CREATE_TABLE); + db.execSQL(KeysModel.UNIFIEDKEYVIEW); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", " @@ -429,16 +431,22 @@ public class KeychainDatabase { } } - case 26: { + case 26: migrateUpdatedKeysToKeyMetadataTable(db); - } - case 27: { + case 27: renameApiAutocryptPeersTable(db); - } + + case 28: + recreateUnifiedKeyView(db); } } + private void recreateUnifiedKeyView(SupportSQLiteDatabase db) { + db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME); + db.execSQL(KeysModel.UNIFIEDKEYVIEW); + } + private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException { LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret"); @@ -456,7 +464,6 @@ public class KeychainDatabase { private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) { try { db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;"); - db.execSQL("UPDATE key_metadata SET last_updated = last_updated * 1000;"); } catch (SQLException e) { if (Constants.DEBUG) { Timber.e(e, "Ignoring migration exception, this probably happened before"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 1e4534125..ebc997f7a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -28,7 +28,6 @@ import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; @@ -62,9 +61,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RING_PUBLIC = 203; private static final int KEY_RING_CERTS = 205; - private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; - private static final int KEY_RINGS_FIND_BY_USER_ID = 402; private static final int KEY_SIGNATURES = 700; @@ -94,19 +91,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe /* * find by criteria other than master key id * - * key_rings/find/email/_ * key_rings/find/subkey/_ * */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_EMAIL + "/*", - KEY_RINGS_FIND_BY_EMAIL); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_SUBKEY + "/*", KEY_RINGS_FIND_BY_SUBKEY); - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_USER_ID + "/*", - KEY_RINGS_FIND_BY_USER_ID); /* * list key_ring specifics @@ -185,9 +175,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe switch (match) { case KEY_RING_UNIFIED: case KEY_RINGS_UNIFIED: - case KEY_RINGS_FIND_BY_EMAIL: - case KEY_RINGS_FIND_BY_SUBKEY: - case KEY_RINGS_FIND_BY_USER_ID: { + case KEY_RINGS_FIND_BY_SUBKEY: { HashMap projectionMap = new HashMap<>(); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); @@ -351,42 +339,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } break; } - case KEY_RINGS_FIND_BY_EMAIL: - case KEY_RINGS_FIND_BY_USER_ID: { - String chunks[] = uri.getLastPathSegment().split(" *, *"); - boolean gotCondition = false; - String emailWhere = ""; - // JAVA ♥ - for (int i = 0; i < chunks.length; ++i) { - if (chunks[i].length() == 0) { - continue; - } - if (i != 0) { - emailWhere += " OR "; - } - if (match == KEY_RINGS_FIND_BY_EMAIL) { - emailWhere += "tmp." + UserPackets.EMAIL + " LIKE " - + DatabaseUtils.sqlEscapeString(chunks[i]); - } else { - emailWhere += "tmp." + UserPackets.USER_ID + " LIKE " - + DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%"); - } - gotCondition = true; - } - if(gotCondition) { - qb.appendWhere(" AND EXISTS (" - + " SELECT 1 FROM " + Tables.USER_PACKETS + " AS tmp" - + " WHERE tmp." + UserPackets.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND (" + emailWhere + ")" - + ")"); - } else { - // TODO better way to do this? - Timber.e("Malformed find by email query!"); - qb.appendWhere(" AND 0"); - } - break; - } } if (TextUtils.isEmpty(sortOrder)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 7de402e69..bf5a1c3a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.remote; + import java.util.ArrayList; import android.app.PendingIntent; @@ -25,7 +26,6 @@ import android.content.Intent; import android.os.Build; import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.remote.ui.RemoteBackupActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteDisplayTransferCodeActivity; import org.sufficientlysecure.keychain.remote.ui.RemoteErrorActivity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java index 3b8741458..b7109b1cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java @@ -207,9 +207,9 @@ class SecurityProblemPresenter { } void onClickViewKey() { - Intent viewKeyIntent = new Intent(context, ViewKeyActivity.class); - viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(viewKeyMasterKeyId)); - context.startActivity(viewKeyIntent); + Intent intent = new Intent(context, ViewKeyActivity.class); + intent.putExtra(ViewKeyActivity.EXTRA_MASTER_KEY_ID, viewKeyMasterKeyId); + context.startActivity(intent); } void onClickOverride() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyInfoLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyInfoLoader.java deleted file mode 100644 index 31c87ac58..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/KeyInfoLoader.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui.dialog; - - -import java.util.List; - -import android.content.ContentResolver; -import android.content.Context; -import android.support.v4.content.AsyncTaskLoader; - -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector; - - -public class KeyInfoLoader extends AsyncTaskLoader> { - private final KeySelector keySelector; - - private List cachedResult; - private KeyInfoInteractor keyInfoInteractor; - - KeyInfoLoader(Context context, ContentResolver contentResolver, KeySelector keySelector) { - super(context); - - this.keySelector = keySelector; - this.keyInfoInteractor = new KeyInfoInteractor(contentResolver); - } - - @Override - public List loadInBackground() { - return keyInfoInteractor.loadKeyInfo(keySelector); - } - - @Override - public void deliverResult(List keySubkeyStatus) { - cachedResult = keySubkeyStatus; - - if (isStarted()) { - super.deliverResult(keySubkeyStatus); - } - } - - @Override - protected void onStartLoading() { - if (cachedResult != null) { - deliverResult(cachedResult); - } - - if (takeContentChanged() || cachedResult == null) { - forceLoad(); - } - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - - cachedResult = null; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java index adb079821..1b62788c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java @@ -23,6 +23,9 @@ import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -42,7 +45,6 @@ import android.text.format.DateUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; @@ -50,8 +52,10 @@ import android.widget.TextView; import com.mikepenz.materialdrawer.util.KeyboardUtil; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicatePresenter.RemoteDeduplicateView; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; @@ -63,8 +67,6 @@ public class RemoteDeduplicateActivity extends FragmentActivity { public static final String EXTRA_PACKAGE_NAME = "package_name"; public static final String EXTRA_DUPLICATE_EMAILS = "duplicate_emails"; - public static final int LOADER_ID_KEYS = 0; - private RemoteDeduplicatePresenter presenter; @@ -73,7 +75,7 @@ public class RemoteDeduplicateActivity extends FragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - presenter = new RemoteDeduplicatePresenter(getBaseContext(), LOADER_ID_KEYS); + presenter = new RemoteDeduplicatePresenter(getBaseContext(), this); KeyboardUtil.hideKeyboard(this); @@ -92,8 +94,43 @@ public class RemoteDeduplicateActivity extends FragmentActivity { String duplicateAddress = dupAddresses.get(0); String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - presenter.setupFromIntentData(packageName, duplicateAddress); - presenter.startLoaders(getSupportLoaderManager()); + DeduplicateViewModel viewModel = ViewModelProviders.of(this).get(DeduplicateViewModel.class); + viewModel.setDuplicateAddress(duplicateAddress); + viewModel.setPackageName(packageName); + + presenter.setupFromViewModel(viewModel); + } + + public static class DeduplicateViewModel extends ViewModel { + private String duplicateAddress; + private LiveData> keyInfoLiveData; + private String packageName; + + public LiveData> getKeyInfoLiveData(Context context) { + if (keyInfoLiveData == null) { + keyInfoLiveData = new GenericLiveData<>(context, null, () -> { + KeyRepository keyRepository = KeyRepository.create(context); + return keyRepository.getUnifiedKeyInfosByMailAddress(duplicateAddress); + }); + } + return keyInfoLiveData; + } + + public void setDuplicateAddress(String duplicateAddress) { + this.duplicateAddress = duplicateAddress; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getPackageName() { + return packageName; + } + + public String getDuplicateAddress() { + return duplicateAddress; + } } public static class RemoteDeduplicateDialogFragment extends DialogFragment { @@ -107,7 +144,7 @@ public class RemoteDeduplicateActivity extends FragmentActivity { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); + Activity activity = requireActivity(); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); @@ -135,7 +172,7 @@ public class RemoteDeduplicateActivity extends FragmentActivity { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - presenter = ((RemoteDeduplicateActivity) getActivity()).presenter; + presenter = ((RemoteDeduplicateActivity) requireActivity()).presenter; presenter.setView(mvpView); } @@ -202,7 +239,7 @@ public class RemoteDeduplicateActivity extends FragmentActivity { } @Override - public void setKeyListData(List data) { + public void setKeyListData(List data) { keyChoiceAdapter.setData(data); } @@ -219,34 +256,17 @@ public class RemoteDeduplicateActivity extends FragmentActivity { } private void setupListenersForPresenter() { - buttonSelect.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - presenter.onClickSelect(); - } - }); - - buttonCancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - presenter.onClickCancel(); - } - }); - + buttonSelect.setOnClickListener(view -> presenter.onClickSelect()); + buttonCancel.setOnClickListener(view -> presenter.onClickCancel()); keyChoiceList.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), - new RecyclerItemClickListener.OnItemClickListener() { - @Override - public void onItemClick(View view, int position) { - presenter.onKeyItemClick(position); - } - })); + (view, position) -> presenter.onKeyItemClick(position))); } } private static class KeyChoiceAdapter extends Adapter { private final LayoutInflater layoutInflater; private final Resources resources; - private List data; + private List data; private Drawable iconUnselected; private Drawable iconSelected; private Integer activeItem; @@ -256,15 +276,16 @@ public class RemoteDeduplicateActivity extends FragmentActivity { this.resources = resources; } + @NonNull @Override - public KeyChoiceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false); return new KeyChoiceViewHolder(keyChoiceItemView); } @Override - public void onBindViewHolder(KeyChoiceViewHolder holder, int position) { - KeyInfo keyInfo = data.get(position); + public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { + UnifiedKeyInfo keyInfo = data.get(position); Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; holder.bind(keyInfo, icon); } @@ -274,7 +295,7 @@ public class RemoteDeduplicateActivity extends FragmentActivity { return data != null ? data.size() : 0; } - public void setData(List data) { + public void setData(List data) { this.data = data; notifyDataSetChanged(); } @@ -319,11 +340,11 @@ public class RemoteDeduplicateActivity extends FragmentActivity { vIcon = itemView.findViewById(R.id.key_list_item_icon); } - void bind(KeyInfo keyInfo, Drawable selectionIcon) { - vName.setText(keyInfo.getName()); + void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { + vName.setText(keyInfo.name()); Context context = vCreation.getContext(); - String dateTime = DateUtils.formatDateTime(context, keyInfo.getCreationDate(), + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index a3ecadbe1..fd8a7ebae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -20,62 +20,59 @@ package org.sufficientlysecure.keychain.remote.ui.dialog; import java.util.List; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.remote.AutocryptInteractor; +import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicateActivity.DeduplicateViewModel; import timber.log.Timber; -class RemoteDeduplicatePresenter implements LoaderCallbacks> { +class RemoteDeduplicatePresenter { private final PackageManager packageManager; private final Context context; - private final int loaderId; + private final LifecycleOwner lifecycleOwner; private AutocryptInteractor autocryptInteractor; - private String duplicateAddress; + private DeduplicateViewModel viewModel; private RemoteDeduplicateView view; private Integer selectedItem; - private List keyInfoData; + private List keyInfoData; - RemoteDeduplicatePresenter(Context context, int loaderId) { + RemoteDeduplicatePresenter(Context context, LifecycleOwner lifecycleOwner) { this.context = context; + this.lifecycleOwner = lifecycleOwner; packageManager = context.getPackageManager(); - - this.loaderId = loaderId; } public void setView(RemoteDeduplicateView view) { this.view = view; } - void setupFromIntentData(String packageName, String duplicateAddress) { + void setupFromViewModel(DeduplicateViewModel viewModel) { + this.viewModel = viewModel; + this.autocryptInteractor = AutocryptInteractor.getInstance(context, viewModel.getPackageName()); + try { - setPackageInfo(packageName); + setPackageInfo(viewModel.getPackageName()); } catch (NameNotFoundException e) { Timber.e("Unable to find info of calling app!"); view.finishAsCancelled(); return; } - this.autocryptInteractor = AutocryptInteractor.getInstance(context, packageName); + view.setAddressText(viewModel.getDuplicateAddress()); - this.duplicateAddress = duplicateAddress; - view.setAddressText(duplicateAddress); + viewModel.getKeyInfoLiveData(context).observe(lifecycleOwner, this::onLoadKeyInfos); } private void setPackageInfo(String packageName) throws NameNotFoundException { @@ -85,31 +82,11 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { view.setTitleClientIcon(appIcon); } - void startLoaders(LoaderManager loaderManager) { - loaderManager.restartLoader(loaderId, null, this); - } - - @Override - public Loader> onCreateLoader(int id, Bundle args) { - KeySelector keySelector = KeySelector.create( - KeyRings.buildUnifiedKeyRingsFindByEmailUri(duplicateAddress), null); - - return new KeyInfoLoader(context, context.getContentResolver(), keySelector); - } - - @Override - public void onLoadFinished(Loader> loader, List data) { + private void onLoadKeyInfos(List data) { this.keyInfoData = data; view.setKeyListData(data); } - @Override - public void onLoaderReset(Loader loader) { - if (view != null) { - view.setKeyListData(null); - } - } - void onClickSelect() { if (keyInfoData == null) { Timber.e("got click on select with no data…?"); @@ -120,8 +97,8 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { return; } - long masterKeyId = keyInfoData.get(selectedItem).getMasterKeyId(); - autocryptInteractor.updateKeyGossipFromDedup(duplicateAddress, masterKeyId); + long masterKeyId = keyInfoData.get(selectedItem).master_key_id(); + autocryptInteractor.updateKeyGossipFromDedup(viewModel.getDuplicateAddress(), masterKeyId); view.finish(); } @@ -151,7 +128,7 @@ class RemoteDeduplicatePresenter implements LoaderCallbacks> { void setAddressText(String text); void setTitleClientIcon(Drawable drawable); - void setKeyListData(List data); + void setKeyListData(List data); void setActiveItem(Integer position); void setEnableSelectButton(boolean enabled); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java index 9c722885a..eecb21837 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java @@ -23,6 +23,9 @@ import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -42,7 +45,6 @@ import android.text.format.DateUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; @@ -51,8 +53,10 @@ import android.widget.TextView; import com.mikepenz.materialdrawer.util.KeyboardUtil; import org.openintents.ssh.authentication.SshAuthenticationApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; @@ -64,8 +68,6 @@ import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickLis public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { public static final String EXTRA_PACKAGE_NAME = "package_name"; - public static final int LOADER_ID_KEYS = 0; - private RemoteSelectAuthenticationKeyPresenter presenter; private String packageName; @@ -75,7 +77,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - presenter = new RemoteSelectAuthenticationKeyPresenter(getBaseContext(), LOADER_ID_KEYS); + presenter = new RemoteSelectAuthenticationKeyPresenter(getBaseContext(), this); KeyboardUtil.hideKeyboard(this); @@ -92,8 +94,33 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { Intent intent = getIntent(); packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - presenter.setupFromIntentData(packageName); - presenter.startLoaders(getSupportLoaderManager()); + SelectAuthKeyViewModel viewModel = ViewModelProviders.of(this).get(SelectAuthKeyViewModel.class); + viewModel.setPackageName(packageName); + + presenter.setupFromViewModel(viewModel); + } + + public static class SelectAuthKeyViewModel extends ViewModel { + private LiveData> keyInfoLiveData; + private String packageName; + + public LiveData> getKeyInfoLiveData(Context context) { + if (keyInfoLiveData == null) { + keyInfoLiveData = new GenericLiveData<>(context, null, () -> { + KeyRepository keyRepository = KeyRepository.create(context); + return keyRepository.getAllUnifiedKeyInfoWithSecret(); + }); + } + return keyInfoLiveData; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getPackageName() { + return packageName; + } } private void onKeySelected(long masterKeyId) { @@ -121,7 +148,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); + Activity activity = requireActivity(); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); @@ -149,7 +176,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - presenter = ((RemoteSelectAuthenticationKeyActivity) getActivity()).presenter; + presenter = ((RemoteSelectAuthenticationKeyActivity) requireActivity()).presenter; presenter.setView(mvpView); } @@ -207,7 +234,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { } @Override - public void setKeyListData(List data) { + public void setKeyListData(List data) { keyChoiceAdapter.setData(data); } @@ -224,34 +251,17 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { } private void setupListenersForPresenter() { - buttonSelect.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - presenter.onClickSelect(); - } - }); - - buttonCancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - presenter.onClickCancel(); - } - }); - + buttonSelect.setOnClickListener(view -> presenter.onClickSelect()); + buttonCancel.setOnClickListener(view -> presenter.onClickCancel()); keyChoiceList.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), - new RecyclerItemClickListener.OnItemClickListener() { - @Override - public void onItemClick(View view, int position) { - presenter.onKeyItemClick(position); - } - })); + (view, position) -> presenter.onKeyItemClick(position))); } } private static class KeyChoiceAdapter extends Adapter { private final LayoutInflater layoutInflater; private final Resources resources; - private List data; + private List data; private Drawable iconUnselected; private Drawable iconSelected; private Integer activeItem; @@ -261,15 +271,16 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { this.resources = resources; } + @NonNull @Override - public KeyChoiceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View keyChoiceItemView = layoutInflater.inflate(R.layout.authentication_key_item, parent, false); return new KeyChoiceViewHolder(keyChoiceItemView); } @Override - public void onBindViewHolder(KeyChoiceViewHolder holder, int position) { - KeyInfo keyInfo = data.get(position); + public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { + UnifiedKeyInfo keyInfo = data.get(position); Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; holder.bind(keyInfo, icon); } @@ -279,7 +290,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { return data != null ? data.size() : 0; } - public void setData(List data) { + public void setData(List data) { this.data = data; notifyDataSetChanged(); } @@ -324,11 +335,11 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { vIcon = itemView.findViewById(R.id.key_list_item_icon); } - void bind(KeyInfo keyInfo, Drawable selectionIcon) { - vName.setText(keyInfo.getName()); + void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { + vName.setText(keyInfo.name()); Context context = vCreation.getContext(); - String dateTime = DateUtils.formatDateTime(context, keyInfo.getCreationDate(), + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyPresenter.java index c9e0f70b0..24f93520a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyPresenter.java @@ -20,52 +20,49 @@ package org.sufficientlysecure.keychain.remote.ui.dialog; import java.util.List; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyActivity.SelectAuthKeyViewModel; import timber.log.Timber; -class RemoteSelectAuthenticationKeyPresenter implements LoaderCallbacks> { +class RemoteSelectAuthenticationKeyPresenter { private final PackageManager packageManager; + private final LifecycleOwner lifecycleOwner; private final Context context; - private final int loaderId; private RemoteSelectAuthenticationKeyView view; private Integer selectedItem; - private List keyInfoData; + private List keyInfoData; - RemoteSelectAuthenticationKeyPresenter(Context context, int loaderId) { + RemoteSelectAuthenticationKeyPresenter(Context context, LifecycleOwner lifecycleOwner) { this.context = context; + this.lifecycleOwner = lifecycleOwner; packageManager = context.getPackageManager(); - - this.loaderId = loaderId; } public void setView(RemoteSelectAuthenticationKeyView view) { this.view = view; } - void setupFromIntentData(String packageName) { + void setupFromViewModel(SelectAuthKeyViewModel viewModel) { try { - setPackageInfo(packageName); + setPackageInfo(viewModel.getPackageName()); } catch (NameNotFoundException e) { Timber.e("Unable to find info of calling app!"); view.finishAsCancelled(); } + + viewModel.getKeyInfoLiveData(context).observe(lifecycleOwner, this::onLoadKeyInfos); } private void setPackageInfo(String packageName) throws NameNotFoundException { @@ -75,31 +72,11 @@ class RemoteSelectAuthenticationKeyPresenter implements LoaderCallbacks> onCreateLoader(int id, Bundle args) { - String selection = KeyRings.HAS_AUTHENTICATE_SECRET + " != 0"; - KeySelector keySelector = KeySelector.create( - KeyRings.buildUnifiedKeyRingsUri(), selection); - return new KeyInfoLoader(context, context.getContentResolver(), keySelector); - } - - @Override - public void onLoadFinished(Loader> loader, List data) { + private void onLoadKeyInfos(List data) { this.keyInfoData = data; view.setKeyListData(data); } - @Override - public void onLoaderReset(Loader loader) { - if (view != null) { - view.setKeyListData(null); - } - } - void onClickSelect() { if (keyInfoData == null) { Timber.e("got click on select with no data…?"); @@ -110,7 +87,7 @@ class RemoteSelectAuthenticationKeyPresenter implements LoaderCallbacks data); + void setKeyListData(List data); void setActiveItem(Integer position); void setEnableSelectButton(boolean enabled); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java index fa9674e73..790a830d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java @@ -23,6 +23,8 @@ import java.util.List; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.DialogInterface; @@ -58,8 +60,11 @@ import android.widget.Toast; import com.mikepenz.materialdrawer.util.KeyboardUtil; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.livedata.PgpKeyGenerationLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectIdentityKeyPresenter.RemoteSelectIdentityKeyView; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.MainActivity; @@ -89,13 +94,18 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - RemoteSelectIdViewModel viewModel = - ViewModelProviders.of(this).get(RemoteSelectIdViewModel.class); - - presenter = new RemoteSelectIdentityKeyPresenter(getBaseContext(), viewModel, this); + presenter = new RemoteSelectIdentityKeyPresenter(getBaseContext(), this); KeyboardUtil.hideKeyboard(this); + RemoteSelectIdViewModel viewModel = ViewModelProviders.of(this).get(RemoteSelectIdViewModel.class); + + Intent intent = getIntent(); + viewModel.rawUserId = intent.getStringExtra(EXTRA_USER_ID); + viewModel.packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + viewModel.packageSignature = intent.getByteArrayExtra(EXTRA_PACKAGE_SIGNATURE); + viewModel.clientHasAutocryptSetupMsg = intent.getBooleanExtra(EXTRA_SHOW_AUTOCRYPT_HINT, false); + if (savedInstanceState == null) { RemoteSelectIdentityKeyDialogFragment frag = new RemoteSelectIdentityKeyDialogFragment(); frag.show(getSupportFragmentManager(), "requestKeyDialog"); @@ -106,13 +116,45 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { protected void onStart() { super.onStart(); - Intent intent = getIntent(); - String userId = intent.getStringExtra(EXTRA_USER_ID); - String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - byte[] packageSignature = intent.getByteArrayExtra(EXTRA_PACKAGE_SIGNATURE); - boolean showAutocryptHint = intent.getBooleanExtra(EXTRA_SHOW_AUTOCRYPT_HINT, false); + RemoteSelectIdViewModel viewModel = ViewModelProviders.of(this).get(RemoteSelectIdViewModel.class); + presenter.setupFromViewModel(viewModel); + } + + public static class RemoteSelectIdViewModel extends ViewModel { + public String packageName; + public byte[] packageSignature; + public String rawUserId; + public boolean clientHasAutocryptSetupMsg; + + public List filteredKeyInfo; + + private LiveData> keyInfo; + private PgpKeyGenerationLiveData keyGenerationData; + private boolean listAllKeys; + + public LiveData> getSecretUnifiedKeyInfo(Context context) { + if (keyInfo == null) { + KeyRepository keyRepository = KeyRepository.create(context); + keyInfo = new GenericLiveData<>(context, null, keyRepository::getAllUnifiedKeyInfoWithSecret); + } + return keyInfo; + } + + public PgpKeyGenerationLiveData getKeyGenerationLiveData(Context context) { + if (keyGenerationData == null) { + keyGenerationData = new PgpKeyGenerationLiveData(context); + } + return keyGenerationData; + } + + public boolean isListAllKeys() { + return listAllKeys; + } + + public void setListAllKeys(boolean listAllKeys) { + this.listAllKeys = listAllKeys; + } - presenter.setupFromIntentData(packageName, packageSignature, userId, showAutocryptHint); } public static class RemoteSelectIdentityKeyDialogFragment extends DialogFragment { @@ -135,7 +177,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); + Activity activity = requireActivity(); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); @@ -177,7 +219,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - presenter = ((RemoteSelectIdKeyActivity) getActivity()).presenter; + presenter = ((RemoteSelectIdKeyActivity) requireActivity()).presenter; presenter.setView(mvpView); } @@ -203,7 +245,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { @NonNull private RemoteSelectIdentityKeyView createMvpView(final ViewGroup rootView, LayoutInflater layoutInflater) { // final ImageView iconClientApp = rootView.findViewById(R.id.icon_client_app); - final KeyChoiceAdapter keyChoiceAdapter = new KeyChoiceAdapter(layoutInflater, getResources()); + final KeyChoiceAdapter keyChoiceAdapter = new KeyChoiceAdapter(layoutInflater); final TextView titleText = rootView.findViewById(R.id.text_title_select_key); final TextView addressText = rootView.findViewById(R.id.text_user_id); final TextView autocryptHint = rootView.findViewById(R.id.key_import_autocrypt_hint); @@ -309,7 +351,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { } @Override - public void setKeyListData(List data) { + public void setKeyListData(List data) { keyChoiceAdapter.setData(data); } @@ -405,19 +447,18 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { private static class KeyChoiceAdapter extends Adapter { private final LayoutInflater layoutInflater; - private final Resources resources; - private List data; + private List data; private Drawable iconUnselected; private Drawable iconSelected; private Integer activeItem; - KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { + KeyChoiceAdapter(LayoutInflater layoutInflater) { this.layoutInflater = layoutInflater; - this.resources = resources; } + @NonNull @Override - public KeyChoiceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View keyChoiceItemView = layoutInflater.inflate(R.layout.api_select_identity_item, parent, false); return new KeyChoiceViewHolder(keyChoiceItemView); } @@ -428,8 +469,8 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { } @Override - public void onBindViewHolder(KeyChoiceViewHolder holder, int position) { - KeyInfo keyInfo = data.get(position); + public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { + UnifiedKeyInfo keyInfo = data.get(position); boolean hasActiveItem = activeItem != null; boolean isActiveItem = hasActiveItem && position == activeItem; @@ -444,7 +485,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { return data != null ? data.size() : 0; } - public void setData(List data) { + public void setData(List data) { this.data = data; notifyDataSetChanged(); } @@ -469,11 +510,11 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { vIcon = itemView.findViewById(R.id.key_list_item_icon); } - void bind(KeyInfo keyInfo, Drawable selectionIcon) { + void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { Context context = vCreation.getContext(); - String email = keyInfo.getEmail(); - String name = keyInfo.getName(); + String email = keyInfo.email(); + String name = keyInfo.name(); if (email != null) { vName.setText(context.getString(R.string.use_key, email)); } else if (name != null) { @@ -482,7 +523,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { vName.setText(context.getString(R.string.use_key_no_name)); } - String dateTime = DateUtils.formatDateTime(context, keyInfo.getCreationDate(), + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdViewModel.java deleted file mode 100644 index d287129fb..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdViewModel.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.sufficientlysecure.keychain.remote.ui.dialog; - - -import android.arch.lifecycle.ViewModel; -import android.content.Context; - -import org.sufficientlysecure.keychain.livedata.KeyInfoLiveData; -import org.sufficientlysecure.keychain.livedata.PgpKeyGenerationLiveData; - - -public class RemoteSelectIdViewModel extends ViewModel { - - private KeyInfoLiveData keyInfo; - private PgpKeyGenerationLiveData keyGenerationData; - private boolean listAllKeys; - - public KeyInfoLiveData getKeyInfo(Context context) { - if (keyInfo == null) { - keyInfo = new KeyInfoLiveData(context, context.getContentResolver()); - } - return keyInfo; - } - - public PgpKeyGenerationLiveData getKeyGenerationLiveData(Context context) { - if (keyGenerationData == null) { - keyGenerationData = new PgpKeyGenerationLiveData(context); - } - return keyGenerationData; - } - - public boolean isListAllKeys() { - return listAllKeys; - } - - public void setListAllKeys(boolean listAllKeys) { - this.listAllKeys = listAllKeys; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java index b9856367e..460546e70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.remote.ui.dialog; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import android.arch.lifecycle.LifecycleOwner; @@ -27,19 +28,18 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; -import android.net.Uri; +import android.text.TextUtils; import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo; -import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector; import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectIdKeyActivity.RemoteSelectIdViewModel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import timber.log.Timber; @@ -47,12 +47,12 @@ import timber.log.Timber; class RemoteSelectIdentityKeyPresenter { private final PackageManager packageManager; + private LifecycleOwner lifecycleOwner; private final Context context; - private final RemoteSelectIdViewModel viewModel; - private RemoteSelectIdentityKeyView view; - private List keyInfoData; + private RemoteSelectIdViewModel viewModel; + private List keyInfoData; private UserId userId; private long selectedMasterKeyId; @@ -61,41 +61,35 @@ class RemoteSelectIdentityKeyPresenter { private ApiApp apiApp; - RemoteSelectIdentityKeyPresenter(Context context, RemoteSelectIdViewModel viewModel, LifecycleOwner lifecycleOwner) { + RemoteSelectIdentityKeyPresenter(Context context, LifecycleOwner lifecycleOwner) { this.context = context; - this.viewModel = viewModel; + this.lifecycleOwner = lifecycleOwner; this.apiAppDao = ApiAppDao.getInstance(context); packageManager = context.getPackageManager(); - - viewModel.getKeyGenerationLiveData(context).observe(lifecycleOwner, this::onChangeKeyGeneration); - viewModel.getKeyInfo(context).observe(lifecycleOwner, this::onChangeKeyInfoData); } public void setView(RemoteSelectIdentityKeyView view) { this.view = view; } - void setupFromIntentData(String packageName, byte[] packageSignature, String rawUserId, boolean clientHasAutocryptSetupMsg) { + void setupFromViewModel(RemoteSelectIdViewModel viewModel) { + this.viewModel = viewModel; + try { - setPackageInfo(packageName, packageSignature); + setPackageInfo(viewModel.packageName, viewModel.packageSignature); } catch (NameNotFoundException e) { Timber.e(e, "Unable to find info of calling app!"); view.finishAsCancelled(); return; } - this.userId = OpenPgpUtils.splitUserId(rawUserId); + this.userId = OpenPgpUtils.splitUserId(viewModel.rawUserId); view.setAddressText(userId.email); - view.setShowAutocryptHint(clientHasAutocryptSetupMsg); + view.setShowAutocryptHint(viewModel.clientHasAutocryptSetupMsg); - loadKeyInfo(); - } - - private void loadKeyInfo() { - Uri listedKeyRingUri = viewModel.isListAllKeys() ? - KeyRings.buildUnifiedKeyRingsUri() : KeyRings.buildUnifiedKeyRingsFindByUserIdUri(userId.email); - viewModel.getKeyInfo(context).setKeySelector(KeySelector.createOnlySecret(listedKeyRingUri, null)); + viewModel.getKeyGenerationLiveData(context).observe(lifecycleOwner, this::onChangeKeyGeneration); + viewModel.getSecretUnifiedKeyInfo(context).observe(lifecycleOwner, this::onChangeKeyInfoData); } private void setPackageInfo(String packageName, byte[] packageSignature) throws NameNotFoundException { @@ -108,22 +102,41 @@ class RemoteSelectIdentityKeyPresenter { view.setTitleClientIconAndName(appIcon, appLabel); } - private void onChangeKeyInfoData(List data) { + private void onChangeKeyInfoData(List data) { + if (data == null) { + return; + } keyInfoData = data; goToSelectLayout(); } private void goToSelectLayout() { - if (keyInfoData == null) { + List filteredKeyInfoData = + viewModel.isListAllKeys() || TextUtils.isEmpty(userId.email) ? keyInfoData : getFilteredKeyInfo(); + + if (filteredKeyInfoData == null) { view.showLayoutEmpty(); - } else if (keyInfoData.isEmpty()) { + } else if (filteredKeyInfoData.isEmpty()) { view.showLayoutSelectNoKeys(); } else { - view.setKeyListData(keyInfoData); + view.setKeyListData(filteredKeyInfoData); view.showLayoutSelectKeyList(); } } + private List getFilteredKeyInfo() { + if (viewModel.filteredKeyInfo == null) { + viewModel.filteredKeyInfo = new ArrayList<>(); + for (UnifiedKeyInfo unifiedKeyInfo : keyInfoData) { + String emailSearchList = unifiedKeyInfo.user_id_list(); + if (emailSearchList == null || emailSearchList.contains(userId.email)) { + viewModel.filteredKeyInfo.add(unifiedKeyInfo); + } + } + } + return viewModel.filteredKeyInfo; + } + private void onChangeKeyGeneration(PgpEditKeyResult pgpEditKeyResult) { if (pgpEditKeyResult == null) { return; @@ -171,7 +184,7 @@ class RemoteSelectIdentityKeyPresenter { } void onKeyItemClick(int position) { - selectedMasterKeyId = keyInfoData.get(position).getMasterKeyId(); + selectedMasterKeyId = keyInfoData.get(position).master_key_id(); view.highlightKey(position); } @@ -222,8 +235,7 @@ class RemoteSelectIdentityKeyPresenter { public void onClickMenuListAllKeys() { viewModel.setListAllKeys(!viewModel.isListAllKeys()); - loadKeyInfo(); - view.showLayoutSelectKeyList(); + goToSelectLayout(); } public void onClickGoToOpenKeychain() { @@ -246,7 +258,7 @@ class RemoteSelectIdentityKeyPresenter { void showLayoutGenerateOk(); void showLayoutGenerateSave(); - void setKeyListData(List data); + void setKeyListData(List data); void highlightKey(int position); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index 4653b3ded..bdb260e54 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -23,8 +23,9 @@ CREATE TABLE IF NOT EXISTS keys ( keyrings_public(master_key_id) ON DELETE CASCADE ); -selectAllUnifiedKeyInfo: -SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, certs.verified, +unifiedKeyView: +CREATE VIEW unifiedKeyView AS + SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, certs.verified, (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, @@ -36,23 +37,25 @@ SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packet LEFT JOIN certs ON ( keys.master_key_id = certs.master_key_id AND certs.verified = 1 ) LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id ) WHERE keys.rank = 0 - GROUP BY keys.master_key_id - ORDER BY has_secret DESC, user_packets.name COLLATE NOCASE ASC; + GROUP BY keys.master_key_id; + +selectAllUnifiedKeyInfo: +SELECT * FROM unifiedKeyView + ORDER BY has_any_secret_int DESC, name COLLATE NOCASE ASC, creation DESC; selectUnifiedKeyInfoByMasterKeyId: -SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packets.name, user_packets.email, user_packets.comment, keys.creation, keys.expiry, keys.is_revoked, keys.is_secure, certs.verified, - (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, - (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, - (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, - (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_encrypt != 0 )) AS has_encrypt_key_int, - GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, - GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list - FROM keys - INNER JOIN user_packets ON ( keys.master_key_id = user_packets.master_key_id AND user_packets.type IS NULL ) - LEFT JOIN certs ON ( keys.master_key_id = certs.master_key_id AND certs.verified = 1 ) - LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id ) - WHERE keys.rank = 0 AND keys.master_key_id = ? - GROUP BY keys.master_key_id; +SELECT * FROM unifiedKeyView + WHERE is_revoked = 0 AND master_key_id = ?; + +selectUnifiedKeyInfoSearchMailAddress: +SELECT * FROM unifiedKeyView + WHERE email LIKE ? + ORDER BY creation DESC; + +selectAllUnifiedKeyInfoWithSecret: +SELECT * FROM unifiedKeyView + WHERE has_any_secret_int = 1 + ORDER BY creation DESC; selectSubkeysByMasterKeyId: SELECT * From c8481d0247d7c897c9aee8c4ab243440702b958e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 14:47:44 +0200 Subject: [PATCH 041/124] use Enum for verification status of certificates --- .../keychain/model/Certification.java | 2 +- .../keychain/model/CustomColumnAdapters.java | 28 ++++++++++++++++++- .../keychain/model/SubKey.java | 10 ++++--- .../keychain/model/UserPacket.java | 15 +++++----- .../keychain/pgp/CanonicalizedKeyRing.java | 10 +++++-- .../pgp/CanonicalizedPublicKeyRing.java | 4 +-- .../pgp/CanonicalizedSecretKeyRing.java | 4 +-- .../keychain/pgp/KeyRing.java | 3 +- .../pgp/OpenPgpSignatureResultBuilder.java | 3 +- .../keychain/pgp/UncachedKeyRing.java | 5 ++-- .../provider/CachedPublicKeyRing.java | 6 ++-- .../keychain/provider/KeyRepository.java | 19 ++++++++----- .../provider/KeyWritableRepository.java | 17 ++++++----- .../keychain/provider/KeychainContract.java | 1 - .../ui/ViewKeyAdvUserIdsFragment.java | 3 +- .../ui/adapter/ImportKeysAdapter.java | 4 ++- .../keychain/ui/adapter/UserIdsAdapter.java | 10 +++---- .../org/sufficientlysecure/keychain/Certs.sq | 3 +- .../operations/CertifyOperationTest.java | 12 ++++---- 19 files changed, 103 insertions(+), 56 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java index b9c72596e..ee42c460e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/Certification.java @@ -8,7 +8,7 @@ import org.sufficientlysecure.keychain.CertsModel; @AutoValue public abstract class Certification implements CertsModel { public static final CertsModel.Factory FACTORY = - new CertsModel.Factory<>(AutoValue_Certification::new); + new CertsModel.Factory<>(AutoValue_Certification::new, CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER); public static final SelectVerifyingCertDetailsMapper CERT_DETAILS_MAPPER = new SelectVerifyingCertDetailsMapper<>(AutoValue_Certification_CertDetails::new); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java index 4561f45fe..bfaa83caa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/CustomColumnAdapters.java @@ -7,10 +7,10 @@ import android.support.annotation.NonNull; import com.squareup.sqldelight.ColumnAdapter; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; - public final class CustomColumnAdapters { private CustomColumnAdapters() { } @@ -64,4 +64,30 @@ public final class CustomColumnAdapters { return (long) value.getNum(); } }; + + public static final ColumnAdapter VERIFICATON_STATUS_ADAPTER = new ColumnAdapter() { + @NonNull + @Override + public VerificationStatus decode(Long databaseValue) { + if (databaseValue == null) { + return VerificationStatus.UNVERIFIED; + } + switch (databaseValue.intValue()) { + case 0: return VerificationStatus.UNVERIFIED; + case 1: return VerificationStatus.VERIFIED_SECRET; + case 2: return VerificationStatus.VERIFIED_SELF; + default: throw new IllegalArgumentException(); + } + } + + @Override + public Long encode(@NonNull VerificationStatus value) { + switch (value) { + case UNVERIFIED: return 0L; + case VERIFIED_SECRET: return 1L; + case VERIFIED_SELF: return 2L; + default: throw new IllegalArgumentException(); + } + } + }; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index b834bcdb2..06508b7ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -8,6 +8,7 @@ import java.util.List; import com.google.auto.value.AutoValue; import com.squareup.sqldelight.RowMapper; import org.sufficientlysecure.keychain.KeysModel; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; @@ -15,8 +16,9 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; public abstract class SubKey implements KeysModel { public static final Factory FACTORY = new Factory<>(AutoValue_SubKey::new, CustomColumnAdapters.SECRET_KEY_TYPE_ADAPTER); - public static final UnifiedKeyViewMapper UNIFIED_KEY_INFO_MAPPER = - FACTORY.selectUnifiedKeyInfoByMasterKeyIdMapper(AutoValue_SubKey_UnifiedKeyInfo::new); + public static final UnifiedKeyViewMapper UNIFIED_KEY_INFO_MAPPER = + FACTORY.selectAllUnifiedKeyInfoMapper( + AutoValue_SubKey_UnifiedKeyInfo::new, Certification.FACTORY); public static Mapper SUBKEY_MAPPER = new Mapper<>(FACTORY); public static RowMapper SKT_MAPPER = FACTORY.selectSecretKeyTypeMapper(); @@ -38,8 +40,8 @@ public abstract class SubKey implements KeysModel { } public boolean is_verified() { - Integer verified = verified(); - return verified != null && verified == 1; + VerificationStatus verified = verified(); + return verified != null && verified == VerificationStatus.VERIFIED_SECRET; } public boolean has_duplicate() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java index 7a9291a97..cf33fc66d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/UserPacket.java @@ -3,30 +3,29 @@ package org.sufficientlysecure.keychain.model; import com.google.auto.value.AutoValue; import org.sufficientlysecure.keychain.UserPacketsModel; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; @AutoValue public abstract class UserPacket implements UserPacketsModel { public static final Factory FACTORY = new Factory<>(AutoValue_UserPacket::new); - public static final SelectUserIdsByMasterKeyIdMapper USER_ID_MAPPER = - FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new); - public static final SelectUserAttributesByTypeAndMasterKeyIdMapper USER_ATTRIBUTE_MAPPER = - FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new); + public static final SelectUserIdsByMasterKeyIdMapper USER_ID_MAPPER = + FACTORY.selectUserIdsByMasterKeyIdMapper(AutoValue_UserPacket_UserId::new, Certification.FACTORY); + public static final SelectUserAttributesByTypeAndMasterKeyIdMapper USER_ATTRIBUTE_MAPPER = + FACTORY.selectUserAttributesByTypeAndMasterKeyIdMapper(AutoValue_UserPacket_UserAttribute::new, Certification.FACTORY); @AutoValue public static abstract class UserId implements SelectUserIdsByMasterKeyIdModel { public boolean isVerified() { - Integer verified = verified(); - return verified != null && verified == Certs.VERIFIED_SECRET; + return verified() == VerificationStatus.VERIFIED_SECRET; } } @AutoValue public static abstract class UserAttribute implements SelectUserAttributesByTypeAndMasterKeyIdModel { public boolean isVerified() { - Integer verified = verified(); - return verified != null && verified == Certs.VERIFIED_SECRET; + return verified() == VerificationStatus.VERIFIED_SECRET; } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java index c84ff0d8f..7b9661d14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java @@ -43,9 +43,9 @@ import org.sufficientlysecure.keychain.util.IterableIterator; */ public abstract class CanonicalizedKeyRing extends KeyRing { - private final int mVerified; + private final VerificationStatus mVerified; - CanonicalizedKeyRing(int verified) { + CanonicalizedKeyRing(VerificationStatus verified) { mVerified = verified; } @@ -53,7 +53,7 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return getRing().getPublicKey().getKeyID(); } - public int getVerified() { + public VerificationStatus getVerified() { return mVerified; } @@ -194,4 +194,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing { return false; } + public enum VerificationStatus { + UNVERIFIED, VERIFIED_SELF, VERIFIED_SECRET + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 5e7ae1ec0..26ef31e7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -41,12 +41,12 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { private PGPPublicKeyRing mRing; - CanonicalizedPublicKeyRing(PGPPublicKeyRing ring, int verified) { + CanonicalizedPublicKeyRing(PGPPublicKeyRing ring, VerificationStatus verified) { super(verified); mRing = ring; } - public CanonicalizedPublicKeyRing(byte[] blob, int verified) { + public CanonicalizedPublicKeyRing(byte[] blob, VerificationStatus verified) { super(verified); if(mRing == null) { // get first object in block diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java index b59ee917a..c9ccdec30 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java @@ -35,12 +35,12 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing { private PGPSecretKeyRing mRing; - CanonicalizedSecretKeyRing(PGPSecretKeyRing ring, int verified) { + CanonicalizedSecretKeyRing(PGPSecretKeyRing ring, VerificationStatus verified) { super(verified); mRing = ring; } - public CanonicalizedSecretKeyRing(byte[] blob, int verified) + public CanonicalizedSecretKeyRing(byte[] blob, VerificationStatus verified) { super(verified); JcaPGPObjectFactory factory = new JcaPGPObjectFactory(blob); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index 7909cfdc2..e4d605ded 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -21,6 +21,7 @@ import android.text.TextUtils; import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import java.io.Serializable; @@ -58,7 +59,7 @@ public abstract class KeyRing { abstract public boolean hasEncrypt() throws PgpKeyNotFoundException; - abstract public int getVerified() throws PgpKeyNotFoundException; + abstract public VerificationStatus getVerified() throws PgpKeyNotFoundException; /** * Splits userId string into naming part, email part, and comment part diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 53477da28..6ba5d4414 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -26,6 +26,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult; import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; import timber.log.Timber; @@ -123,7 +124,7 @@ public class OpenPgpSignatureResultBuilder { } catch (PgpKeyNotFoundException e) { Timber.d("No primary user id in keyring with master key id " + signingRing.getMasterKeyId()); } - setSignatureKeyCertified(signingRing.getVerified() > 0); + setSignatureKeyCertified(signingRing.getVerified() != VerificationStatus.UNVERIFIED); List allUserIds = signingRing.getUnorderedUserIds(); List confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); 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 93986c5a1..2aa8bd05c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -64,6 +64,7 @@ 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; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -1115,8 +1116,8 @@ public class UncachedKeyRing { log.add(LogType.MSG_KC_SUCCESS, indent); } - return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, 1) - : new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, 0); + return isSecret() ? new CanonicalizedSecretKeyRing((PGPSecretKeyRing) ring, VerificationStatus.VERIFIED_SECRET) + : new CanonicalizedPublicKeyRing((PGPPublicKeyRing) ring, VerificationStatus.UNVERIFIED); } /** This operation merges information from a different keyring, returning a combined diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 57957c64b..ee20d5e6c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -20,6 +20,8 @@ package org.sufficientlysecure.keychain.provider; import android.net.Uri; +import org.sufficientlysecure.keychain.model.CustomColumnAdapters; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -238,12 +240,12 @@ public class CachedPublicKeyRing extends KeyRing { } @Override - public int getVerified() throws PgpKeyNotFoundException { + public VerificationStatus getVerified() throws PgpKeyNotFoundException { try { Object data = mKeyRepository.getGenericData(mUri, KeychainContract.KeyRings.VERIFIED, KeyRepository.FIELD_TYPE_INTEGER); - return ((Long) data).intValue(); + return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode((Long) data); } catch(KeyWritableRepository.NotFoundException e) { throw new PgpKeyNotFoundException(e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index cab949179..b6136c7e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -31,6 +31,8 @@ import android.net.Uri; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.sufficientlysecure.keychain.model.Certification; +import org.sufficientlysecure.keychain.model.CustomColumnAdapters; import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; @@ -38,12 +40,12 @@ import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import timber.log.Timber; @@ -197,10 +199,11 @@ public class KeyRepository extends AbstractDao { try { if (cursor != null && cursor.moveToFirst()) { long masterKeyId = cursor.getLong(0); - int verified = cursor.getInt(1); + long verified = cursor.getLong(1); byte[] publicKeyData = loadPublicKeyRingData(masterKeyId); - return new CanonicalizedPublicKeyRing(publicKeyData, verified); + VerificationStatus verificationStatus = CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified); + return new CanonicalizedPublicKeyRing(publicKeyData, verificationStatus); } else { throw new NotFoundException("Key not found!"); } @@ -221,14 +224,16 @@ public class KeyRepository extends AbstractDao { try { if (cursor != null && cursor.moveToFirst()) { long masterKeyId = cursor.getLong(0); - int verified = cursor.getInt(1); + long verified = cursor.getLong(1); int hasAnySecret = cursor.getInt(2); if (hasAnySecret == 0) { throw new NotFoundException("No secret key available or unknown public key!"); } + VerificationStatus verificationStatus = CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified); + byte[] secretKeyData = loadSecretKeyRingData(masterKeyId); - return new CanonicalizedSecretKeyRing(secretKeyData, verified); + return new CanonicalizedSecretKeyRing(secretKeyData, verificationStatus); } else { throw new NotFoundException("Key not found!"); } @@ -286,8 +291,8 @@ public class KeyRepository extends AbstractDao { public List getConfirmedUserIds(long masterKeyId) { ArrayList userIds = new ArrayList<>(); - SqlDelightQuery query = - UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(masterKeyId, Certs.VERIFIED_SECRET); + SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification( + Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET); for (UserId userId : mapAllRows(query, UserPacket.USER_ID_MAPPER::map)) { userIds.add(userId.user_id()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 008af6e31..38d093a1e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -39,11 +39,13 @@ import android.support.v4.util.LongSparseArray; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.CustomColumnAdapters; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.operations.results.UpdateTrustResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; @@ -134,10 +136,11 @@ public class KeyWritableRepository extends KeyRepository { while (cursor.moveToNext()) { try { long masterKeyId = cursor.getLong(0); - int verified = cursor.getInt(2); + long verified = cursor.getLong(2); byte[] blob = loadPublicKeyRingData(masterKeyId); + VerificationStatus verificationStatus = CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified); if (blob != null) { - result.put(masterKeyId, new CanonicalizedPublicKeyRing(blob, verified).getPublicKey()); + result.put(masterKeyId, new CanonicalizedPublicKeyRing(blob, verificationStatus).getPublicKey()); } } catch (NotFoundException e) { throw new IllegalStateException("Error reading secret key data, this should not happen!", e); @@ -499,7 +502,7 @@ public class KeyWritableRepository extends KeyRepository { if (item.selfRevocation != null) { operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation, - Certs.VERIFIED_SELF)); + VerificationStatus.VERIFIED_SELF)); // don't bother with trusted certs if the uid is revoked, anyways continue; } @@ -509,7 +512,7 @@ public class KeyWritableRepository extends KeyRepository { } operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, - selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); + selfCertsAreTrusted ? VerificationStatus.VERIFIED_SECRET : VerificationStatus.VERIFIED_SELF)); // iterate over signatures for (int i = 0; i < item.trustedCerts.size(); i++) { @@ -521,7 +524,7 @@ public class KeyWritableRepository extends KeyRepository { } // otherwise, build database operation operations.add(buildCertOperations( - masterKeyId, userIdRank, sig, Certs.VERIFIED_SECRET)); + masterKeyId, userIdRank, sig, VerificationStatus.VERIFIED_SECRET)); } } @@ -1064,7 +1067,7 @@ public class KeyWritableRepository extends KeyRepository { * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ private ContentProviderOperation - buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, int verified) + buildCertOperations(long masterKeyId, int rank, WrappedSignature cert, VerificationStatus verificationStatus) throws IOException { ContentValues values = new ContentValues(); values.put(Certs.MASTER_KEY_ID, masterKeyId); @@ -1072,7 +1075,7 @@ public class KeyWritableRepository extends KeyRepository { values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyId()); values.put(Certs.TYPE, cert.getSignatureType()); values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); - values.put(Certs.VERIFIED, verified); + values.put(Certs.VERIFIED, CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.encode(verificationStatus)); values.put(Certs.DATA, cert.getEncoded()); Uri uri = Certs.buildCertsUri(masterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 7085ec839..edb8ea0a9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -179,7 +179,6 @@ public class KeychainContract { } public static class Certs implements CertsColumns, BaseColumns { - public static final int UNVERIFIED = 0; public static final int VERIFIED_SECRET = 1; public static final int VERIFIED_SELF = 2; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index e71cc5917..0877d9263 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; @@ -155,7 +156,7 @@ public class ViewKeyAdvUserIdsFragment extends Fragment { private void showUserIdInfo(final int position) { final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); - final boolean isVerified = mUserIdsAdapter.getIsVerified(position) == Certs.VERIFIED_SECRET; + final boolean isVerified = mUserIdsAdapter.getVerificationStatus(position) == VerificationStatus.VERIFIED_SECRET; DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(() -> { UserIdInfoDialogFragment dialogFragment = diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 31e595161..162743758 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultList import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; @@ -94,7 +95,8 @@ public class ImportKeysAdapter extends RecyclerView.Adapter 0; + VerificationStatus verified = keyRing.getVerified(); + keyState.mVerified = verified != null && verified != VerificationStatus.UNVERIFIED; } catch (KeyRepository.NotFoundException | PgpKeyNotFoundException ignored) { } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 892226b88..518a5ac8c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -34,7 +34,7 @@ import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.UserPacket.UserId; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -163,12 +163,12 @@ public class UserIdsAdapter extends BaseAdapter { vAddress.setTypeface(null, Typeface.NORMAL); } - int isVerified = getIsVerified(position); + VerificationStatus isVerified = getVerificationStatus(position); switch (isVerified) { - case Certs.VERIFIED_SECRET: + case VERIFIED_SECRET: KeyFormattingUtils.setStatusImage(context, vVerified, null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; - case Certs.VERIFIED_SELF: + case VERIFIED_SELF: KeyFormattingUtils.setStatusImage(context, vVerified, null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR); break; default: @@ -216,7 +216,7 @@ public class UserIdsAdapter extends BaseAdapter { return data.get(position).is_revoked(); } - public int getIsVerified(int position) { + public VerificationStatus getVerificationStatus(int position) { return data.get(position).verified(); } } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq index 377135d4c..2674659ee 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq @@ -1,4 +1,5 @@ import java.lang.Integer; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -- TODO implement. this is only here for reference in SQLDelight CREATE TABLE IF NOT EXISTS certs( @@ -6,7 +7,7 @@ CREATE TABLE IF NOT EXISTS certs( rank INTEGER NOT NULL, key_id_certifier INTEGER NOT NULL, type INTEGER NOT NULL, - verified INTEGER AS Integer NOT NULL, + verified INTEGER AS VerificationStatus NOT NULL, creation INTEGER NOT NULL, data BLOB NOT NULL, PRIMARY KEY(master_key_id, rank, key_id_certifier), diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java index 06f79699c..e28c1fc10 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java @@ -37,12 +37,12 @@ import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; @@ -137,7 +137,7 @@ public class CertifyOperationTest { .getCanonicalizedPublicKeyRing(mStaticRing1.getMasterKeyId()); Assert.assertEquals("secret key must be marked self-certified in database", // TODO this should be more correctly be VERIFIED_SELF at some point! - Certs.VERIFIED_SECRET, ring.getVerified()); + VerificationStatus.VERIFIED_SECRET, ring.getVerified()); } @@ -150,7 +150,7 @@ public class CertifyOperationTest { CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); Assert.assertEquals("public key must not be marked verified prior to certification", - Certs.UNVERIFIED, ring.getVerified()); + VerificationStatus.UNVERIFIED, ring.getVerified()); } CertifyActionsParcel.Builder actions = CertifyActionsParcel.builder(mStaticRing1.getMasterKeyId()); @@ -164,7 +164,7 @@ public class CertifyOperationTest { CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); Assert.assertEquals("new key must be verified now", - Certs.VERIFIED_SECRET, ring.getVerified()); + VerificationStatus.UNVERIFIED, ring.getVerified()); } } @@ -178,7 +178,7 @@ public class CertifyOperationTest { CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); Assert.assertEquals("public key must not be marked verified prior to certification", - Certs.UNVERIFIED, ring.getVerified()); + VerificationStatus.UNVERIFIED, ring.getVerified()); } CertifyActionsParcel.Builder actions = CertifyActionsParcel.builder(mStaticRing1.getMasterKeyId()); @@ -192,7 +192,7 @@ public class CertifyOperationTest { CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); Assert.assertEquals("new key must be verified now", - Certs.VERIFIED_SECRET, ring.getVerified()); + VerificationStatus.VERIFIED_SECRET, ring.getVerified()); } } From 4cdc57c690015844c219f3858e2d014d731c07b0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 15:11:08 +0200 Subject: [PATCH 042/124] clean up KeychainDatabase a bit --- .../keychain/AndroidTestHelpers.java | 2 +- .../keychain/ui/EditKeyTest.java | 2 +- .../keychain/livedata/CertificationDao.java | 2 +- .../keychain/provider/ApiAppDao.java | 2 +- .../keychain/provider/AutocryptPeerDao.java | 2 +- .../keychain/provider/KeyMetadataDao.java | 2 +- .../keychain/provider/KeyRepository.java | 2 +- .../provider/KeyWritableRepository.java | 2 +- .../keychain/provider/KeychainDatabase.java | 131 ++++-------------- .../keychain/provider/KeychainProvider.java | 2 +- .../OverriddenWarningsRepository.java | 2 +- .../remote/KeychainExternalProvider.java | 2 +- .../ui/keyview/loader/IdentityDao.java | 2 +- .../keychain/OverriddenWarnings.sq | 4 + .../keychain/provider/InteropTest.java | 2 +- .../provider/KeyRepositorySaveTest.java | 2 +- 16 files changed, 45 insertions(+), 118 deletions(-) create mode 100644 OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java index f5353540d..1e8e78335 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java @@ -152,7 +152,7 @@ public class AndroidTestHelpers { public static void cleanupForTests(Context context) throws Exception { - new KeychainDatabase(context).clearDatabase(); + KeychainDatabase.getInstance(context).clearDatabase(); // import these two, make sure they're there importKeysFromResource(context, "x.sec.asc"); diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java index 10939b8e9..577126300 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java @@ -63,7 +63,7 @@ public class EditKeyTest { public void test01Edit() throws Exception { Activity activity = mActivity.getActivity(); - new KeychainDatabase(activity).clearDatabase(); + KeychainDatabase.getInstance(activity).clearDatabase(); // import key for testing, get a stable initial state importKeysFromResource(activity, "x.sec.asc"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java index e6236bd41..a8287bcb4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java @@ -17,7 +17,7 @@ public class CertificationDao { private final DatabaseNotifyManager databaseNotifyManager; public static CertificationDao getInstance(Context context) { - KeychainDatabase keychainDatabase = new KeychainDatabase(context); + KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); return new CertificationDao(keychainDatabase.getWritableDatabase(), databaseNotifyManager); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java index 10129b3a5..9e9b91493 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java @@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.model.ApiApp; public class ApiAppDao extends AbstractDao { public static ApiAppDao getInstance(Context context) { - KeychainDatabase keychainDatabase = new KeychainDatabase(context); + KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); return new ApiAppDao(keychainDatabase, databaseNotifyManager); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java index 3e9f6e2dc..878ce54c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java @@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; public class AutocryptPeerDao extends AbstractDao { public static AutocryptPeerDao getInstance(Context context) { - KeychainDatabase keychainDatabase = new KeychainDatabase(context); + KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); return new AutocryptPeerDao(keychainDatabase, databaseNotifyManager); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java index 94f458aee..0a53ca7ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java @@ -16,7 +16,7 @@ import org.sufficientlysecure.keychain.model.KeyMetadata; public class KeyMetadataDao extends AbstractDao { public static KeyMetadataDao create(Context context) { - KeychainDatabase database = new KeychainDatabase(context); + KeychainDatabase database = KeychainDatabase.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); return new KeyMetadataDao(database, databaseNotifyManager); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index b6136c7e3..9877a6d86 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -70,7 +70,7 @@ public class KeyRepository extends AbstractDao { ContentResolver contentResolver = context.getContentResolver(); LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); - KeychainDatabase database = new KeychainDatabase(context); + KeychainDatabase database = KeychainDatabase.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); return new KeyRepository(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 38d093a1e..8d42d78cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -93,7 +93,7 @@ public class KeyWritableRepository extends KeyRepository { LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); - KeychainDatabase database = new KeychainDatabase(context); + KeychainDatabase database = KeychainDatabase.getInstance(context); return new KeyWritableRepository(context, database, localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, autocryptPeerDao); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 62e643b7d..da0cbb543 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -32,7 +32,6 @@ import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteException; -import android.provider.BaseColumns; import org.sufficientlysecure.keychain.ApiAppsModel; import org.sufficientlysecure.keychain.AutocryptPeersModel; @@ -42,15 +41,10 @@ import org.sufficientlysecure.keychain.KeyMetadataModel; import org.sufficientlysecure.keychain.KeyRingsPublicModel; import org.sufficientlysecure.keychain.KeySignaturesModel; import org.sufficientlysecure.keychain.KeysModel; +import org.sufficientlysecure.keychain.OverriddenWarningsModel; import org.sufficientlysecure.keychain.UserPacketsModel; -import org.sufficientlysecure.keychain.model.ApiApp; -import org.sufficientlysecure.keychain.model.Certification; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignaturesColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; @@ -68,7 +62,15 @@ public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; private static final int DATABASE_VERSION = 29; private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; - private Context context; + + private static KeychainDatabase sInstance; + + public static KeychainDatabase getInstance(Context context) { + if (sInstance == null) { + sInstance = new KeychainDatabase(context.getApplicationContext()); + } + return sInstance; + } public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; @@ -80,98 +82,24 @@ public class KeychainDatabase { String OVERRIDDEN_WARNINGS = "overridden_warnings"; } - private static final String CREATE_KEYS = - "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " (" - + KeysColumns.MASTER_KEY_ID + " INTEGER, " - + KeysColumns.RANK + " INTEGER, " - - + KeysColumns.KEY_ID + " INTEGER, " - + KeysColumns.KEY_SIZE + " INTEGER, " - + KeysColumns.KEY_CURVE_OID + " TEXT, " - + KeysColumns.ALGORITHM + " INTEGER, " - + KeysColumns.FINGERPRINT + " BLOB, " - - + KeysColumns.CAN_CERTIFY + " INTEGER, " - + KeysColumns.CAN_SIGN + " INTEGER, " - + KeysColumns.CAN_ENCRYPT + " INTEGER, " - + KeysColumns.CAN_AUTHENTICATE + " INTEGER, " - + KeysColumns.IS_REVOKED + " INTEGER, " - + KeysColumns.HAS_SECRET + " INTEGER, " - + KeysColumns.IS_SECURE + " INTEGER, " - - + KeysColumns.CREATION + " INTEGER, " - + KeysColumns.EXPIRY + " INTEGER, " - - + "PRIMARY KEY(" + KeysColumns.MASTER_KEY_ID + ", " + KeysColumns.RANK + ")," - + "FOREIGN KEY(" + KeysColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" - + ")"; - - private static final String CREATE_USER_PACKETS = - "CREATE TABLE IF NOT EXISTS " + Tables.USER_PACKETS + "(" - + UserPacketsColumns.MASTER_KEY_ID + " INTEGER, " - + UserPacketsColumns.TYPE + " INT, " - + UserPacketsColumns.USER_ID + " TEXT, " - + UserPacketsColumns.NAME + " TEXT, " - + UserPacketsColumns.EMAIL + " TEXT, " - + UserPacketsColumns.COMMENT + " TEXT, " - + UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, " - - + UserPacketsColumns.IS_PRIMARY + " INTEGER, " - + UserPacketsColumns.IS_REVOKED + " INTEGER, " - + UserPacketsColumns.RANK+ " INTEGER, " - - + "PRIMARY KEY(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + "), " - + "FOREIGN KEY(" + UserPacketsColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" - + ")"; - - private static final String CREATE_KEY_SIGNATURES = - "CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " (" - + KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " - + KeySignaturesColumns.SIGNER_KEY_ID + " INTEGER NOT NULL, " - + "PRIMARY KEY(" + KeySignaturesColumns.MASTER_KEY_ID + ", " + KeySignaturesColumns.SIGNER_KEY_ID + "), " - + "FOREIGN KEY(" + KeySignaturesColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" - + ")"; - - private static final String CREATE_API_APPS_ALLOWED_KEYS = - "CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + ApiAppsAllowedKeysColumns.KEY_ID + " INTEGER, " - + ApiAppsAllowedKeysColumns.PACKAGE_NAME + " TEXT NOT NULL, " - - + "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", " - + ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), " - + "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES " - + "api_apps (" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" - + ")"; - - private static final String CREATE_OVERRIDDEN_WARNINGS = - "CREATE TABLE IF NOT EXISTS " + Tables.OVERRIDDEN_WARNINGS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + OverriddenWarnings.IDENTIFIER + " TEXT NOT NULL UNIQUE " - + ")"; - - public KeychainDatabase(Context context) { - this.context = context.getApplicationContext(); + private KeychainDatabase(Context context) { supportSQLiteOpenHelper = new FrameworkSQLiteOpenHelperFactory() .create(Configuration.builder(context).name(DATABASE_NAME).callback( new Callback(DATABASE_VERSION) { @Override public void onCreate(SupportSQLiteDatabase db) { - KeychainDatabase.this.onCreate(db); + KeychainDatabase.this.onCreate(db, context); } @Override public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { - KeychainDatabase.this.onUpgrade(db, oldVersion, newVersion); + KeychainDatabase.this.onUpgrade(db, context, oldVersion, newVersion); } @Override public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { - KeychainDatabase.this.onDowngrade(db, oldVersion, newVersion); + KeychainDatabase.this.onDowngrade(); } @Override @@ -193,17 +121,17 @@ public class KeychainDatabase { return supportSQLiteOpenHelper.getWritableDatabase(); } - private void onCreate(SupportSQLiteDatabase db) { + private void onCreate(SupportSQLiteDatabase db, Context context) { Timber.w("Creating database..."); db.execSQL(KeyRingsPublicModel.CREATE_TABLE); - db.execSQL(CREATE_KEYS); + db.execSQL(KeysModel.CREATE_TABLE); db.execSQL(UserPacketsModel.CREATE_TABLE); db.execSQL(CertsModel.CREATE_TABLE); db.execSQL(KeyMetadataModel.CREATE_TABLE); db.execSQL(KeySignaturesModel.CREATE_TABLE); - db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); - db.execSQL(CREATE_OVERRIDDEN_WARNINGS); + db.execSQL(ApiAppsModel.CREATE_TABLE); + db.execSQL(OverriddenWarningsModel.CREATE_TABLE); db.execSQL(AutocryptPeersModel.CREATE_TABLE); db.execSQL(ApiAppsModel.CREATE_TABLE); db.execSQL(KeysModel.UNIFIEDKEYVIEW); @@ -219,7 +147,7 @@ public class KeychainDatabase { Preferences.getPreferences(context).setKeySignaturesTableInitialized(); } - private void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + private void onUpgrade(SupportSQLiteDatabase db, Context context, int oldVersion, int newVersion) { Timber.d("Upgrading db from " + oldVersion + " to " + newVersion); switch (oldVersion) { @@ -258,7 +186,7 @@ public class KeychainDatabase { case 7: // new table for allowed key ids in API try { - db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); + db.execSQL(ApiAppsModel.CREATE_TABLE); } catch (Exception e) { // never mind, the column probably already existed } @@ -336,14 +264,6 @@ public class KeychainDatabase { case 19: // emergency fix for crashing consolidate db.execSQL("UPDATE keys SET is_secure = 1;"); - /* TODO actually drop this table. leaving it around for now! - case 20: - db.execSQL("DROP TABLE api_accounts"); - if (oldVersion == 20) { - // no need to consolidate - return; - } - */ case 20: db.execSQL( "CREATE TABLE IF NOT EXISTS overridden_warnings (" @@ -365,7 +285,7 @@ public class KeychainDatabase { + "last_updated INTEGER NOT NULL, " + "last_seen_key INTEGER NOT NULL, " + "state INTEGER NOT NULL, " - + "master_key_id INTEGER NULL, " + + "master_key_id INTEGER, " + "PRIMARY KEY(package_name, identifier), " + "FOREIGN KEY(package_name) REFERENCES api_apps(package_name) ON DELETE CASCADE" + ")"); @@ -425,7 +345,7 @@ public class KeychainDatabase { case 25: { try { - migrateSecretKeysFromDbToLocalStorage(db); + migrateSecretKeysFromDbToLocalStorage(db, context); } catch (IOException e) { throw new IllegalStateException("Error migrating secret keys! This is bad!!"); } @@ -439,15 +359,18 @@ public class KeychainDatabase { case 28: recreateUnifiedKeyView(db); + // drop old table from version 20 + db.execSQL("DROP TABLE IF EXISTS api_accounts"); } } private void recreateUnifiedKeyView(SupportSQLiteDatabase db) { + // noinspection deprecation db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME); db.execSQL(KeysModel.UNIFIEDKEYVIEW); } - private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException { + private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db, Context context) throws IOException { LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret"); while (cursor.moveToNext()) { @@ -485,7 +408,7 @@ public class KeychainDatabase { } } - public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + private void onDowngrade() { // Downgrade is ok for the debug version, makes it easier to work with branches if (Constants.DEBUG) { return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index ebc997f7a..800a3e5ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -145,7 +145,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public KeychainDatabase getDb() { if(mKeychainDatabase == null) - mKeychainDatabase = new KeychainDatabase(getContext()); + mKeychainDatabase = KeychainDatabase.getInstance(getContext()); return mKeychainDatabase; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java index fe1b23ca5..68cf00861 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java @@ -44,7 +44,7 @@ public class OverriddenWarningsRepository { private KeychainDatabase getDb() { if (keychainDatabase == null) { - keychainDatabase = new KeychainDatabase(context); + keychainDatabase = KeychainDatabase.getInstance(context); } return keychainDatabase; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 29550b3a7..a92adb802 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -135,7 +135,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC String groupBy = null; - SupportSQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase(); + SupportSQLiteDatabase db = KeychainDatabase.getInstance(getContext()).getReadableDatabase(); String callingPackageName = apiPermissionHelper.getCurrentCallingPackage(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index b802c16cd..b02a2ee38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -55,7 +55,7 @@ public class IdentityDao { private final AutocryptPeerDao autocryptPeerDao; public static IdentityDao getInstance(Context context) { - SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase(); + SupportSQLiteDatabase db = KeychainDatabase.getInstance(context).getWritableDatabase(); PackageManager packageManager = context.getPackageManager(); PackageIconGetter iconGetter = PackageIconGetter.getInstance(context); AutocryptPeerDao autocryptPeerDao = AutocryptPeerDao.getInstance(context); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq new file mode 100644 index 000000000..3736a0f85 --- /dev/null +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS overridden_warnings ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + identifier TEXT NOT NULL UNIQUE +); \ No newline at end of file diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index 384f83949..dc4c51955 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -244,7 +244,7 @@ public class InteropTest { KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(verify.getMasterKeyId()) : null; KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application, - new KeychainDatabase(RuntimeEnvironment.application), + KeychainDatabase.getInstance(RuntimeEnvironment.application), LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application), LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application), DatabaseNotifyManager.create(RuntimeEnvironment.application), diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java index 5f0e6b522..c4edf93c1 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java @@ -66,7 +66,7 @@ public class KeyRepositorySaveTest { result = KeyWritableRepository.create(RuntimeEnvironment.application).savePublicKeyRing(second); Assert.assertFalse("second keyring import should fail", result.success()); - new KeychainDatabase(RuntimeEnvironment.application).clearDatabase(); + KeychainDatabase.getInstance(RuntimeEnvironment.application).clearDatabase(); // and the other way around result = KeyWritableRepository.create(RuntimeEnvironment.application).savePublicKeyRing(second); From a64d898716dfb00b51fd8b877c2c99c3a715ae18 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 15:30:45 +0200 Subject: [PATCH 043/124] hide view key fragment cards until data is loaded --- .../keychain/ui/keyview/ViewKeyFragment.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index d81d0d7ea..7df8c2173 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -72,6 +72,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener private SystemContactCardView systemContactCard; private KeyHealthView keyStatusHealth; private KeyserverStatusView keyserverStatusView; + private View keyStatusCardView; IdentityAdapter identitiesAdapter; @@ -90,6 +91,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener identitiesCardView = view.findViewById(R.id.card_identities); systemContactCard = view.findViewById(R.id.linked_system_contact_card); + keyStatusCardView = view.findViewById(R.id.subkey_status_card); keyStatusHealth = view.findViewById(R.id.key_status_health); keyserverStatusView = view.findViewById(R.id.key_status_keyserver); @@ -106,6 +108,9 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener }); identitiesCardView.setIdentitiesAdapter(identitiesAdapter); + identitiesCardView.setVisibility(View.GONE); + keyStatusCardView.setVisibility(View.GONE); + keyStatusHealth.setOnHealthClickListener((v) -> onKeyHealthClick()); return view; @@ -136,6 +141,8 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener return; } + keyStatusCardView.setVisibility(View.VISIBLE); + this.subkeyStatus = subkeyStatus; KeyHealthStatus keyHealthStatus = subkeyStatus.keyHealthStatus; @@ -291,6 +298,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener private void onLoadIdentityInfo(List identityInfos) { identitiesAdapter.setData(identityInfos, unifiedKeyInfo.has_any_secret()); + identitiesCardView.setVisibility(View.VISIBLE); } private void onLoadSystemContact(SystemContactInfo systemContactInfo) { From 1425f3432126e6e6ac8dbb235b375290dd262a96 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 16:59:20 +0200 Subject: [PATCH 044/124] 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 From 4416ddf11c5cb8aaad533de1318a0ade1e4e9ef1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 17:38:26 +0200 Subject: [PATCH 045/124] Get rid of more places where URIs are used for loading --- .../keychain/ui/ViewKeyAdvShareTest.java | 120 ------------------ .../keychain/operations/RevokeOperation.java | 5 +- .../pgp/PgpDecryptVerifyOperation.java | 14 +- .../keychain/pgp/PgpSignEncryptOperation.java | 3 +- .../keychain/pgp/PgpSignatureChecker.java | 17 ++- .../keychain/provider/KeyRepository.java | 81 ++++-------- .../keychain/remote/OpenPgpService.java | 5 +- .../ui/RequestKeyPermissionPresenter.java | 11 +- .../keychain/ui/DecryptFragment.java | 19 +-- .../ui/EncryptModeAsymmetricFragment.java | 4 +- .../keychain/ui/PassphraseDialogActivity.java | 19 +-- .../ui/SecurityTokenOperationActivity.java | 12 +- .../ui/token/PublicKeyRetrievalLoader.java | 12 +- .../org/sufficientlysecure/keychain/Keys.sq | 5 + .../keychain/provider/InteropTest.java | 2 - 15 files changed, 86 insertions(+), 243 deletions(-) delete mode 100644 OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java deleted file mode 100644 index 83463d179..000000000 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2015 Vincent Breitmoser - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - - -import android.app.Activity; -import android.app.Instrumentation.ActivityResult; -import android.content.Intent; -import android.support.test.espresso.intent.rule.IntentsTestRule; - -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Rule; -import org.junit.runners.MethodSorters; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.intent.Intents.intending; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasType; -import static android.support.test.espresso.intent.matcher.UriMatchers.hasHost; -import static android.support.test.espresso.intent.matcher.UriMatchers.hasScheme; -import static android.support.test.espresso.matcher.ViewMatchers.assertThat; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkAndDismissSnackbar; -import static org.sufficientlysecure.keychain.AndroidTestHelpers.cleanupForTests; - -//TODO This test is disabled because it needs to be fixed to work with updated code -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -//@RunWith(AndroidJUnit4.class) -//@LargeTest -public class ViewKeyAdvShareTest { - - @Rule - public final IntentsTestRule mActivityRule - = new IntentsTestRule(ViewKeyAdvActivity.class) { - @Override - protected Intent getActivityIntent() { - Intent intent = super.getActivityIntent(); - intent.setData(KeyRings.buildGenericKeyRingUri(0x9D604D2F310716A3L)); - intent.putExtra(ViewKeyAdvActivity.EXTRA_SELECTED_TAB, ViewKeyAdvActivity.TAB_SHARE); - return intent; - } - }; - private Activity mActivity; - - @Before - public void setUp() throws Exception { - mActivity = mActivityRule.getActivity(); - - cleanupForTests(mActivity); - } - - //@Test - public void testShareOperations() throws Exception { - - // no-op should yield snackbar - onView(withId(R.id.view_key_action_fingerprint_clipboard)).perform(click()); - checkAndDismissSnackbar(Style.OK, R.string.fingerprint_copied_to_clipboard); - assertThat("clipboard data is fingerprint", ClipboardReflection.getClipboardText(mActivity), - is("c619d53f7a5f96f391a84ca79d604d2f310716a3")); - - intending(allOf( - hasAction("android.intent.action.CHOOSER"), - hasExtra(equalTo(Intent.EXTRA_INTENT), allOf( - hasAction(Intent.ACTION_SEND), - hasType("text/plain"), - hasExtra(is(Intent.EXTRA_TEXT), is("openpgp4fpr:c619d53f7a5f96f391a84ca79d604d2f310716a3")), - hasExtra(is(Intent.EXTRA_STREAM), - allOf(hasScheme("content"), hasHost(TemporaryFileProvider.AUTHORITY))) - )) - )).respondWith(new ActivityResult(Activity.RESULT_OK, null)); - onView(withId(R.id.view_key_action_fingerprint_share)).perform(click()); - - onView(withId(R.id.view_key_action_key_clipboard)).perform(click()); - checkAndDismissSnackbar(Style.OK, R.string.key_copied_to_clipboard); - assertThat("clipboard data is key", - ClipboardReflection.getClipboardText(mActivity), startsWith("----")); - - intending(allOf( - hasAction("android.intent.action.CHOOSER"), - hasExtra(equalTo(Intent.EXTRA_INTENT), allOf( - hasAction(Intent.ACTION_SEND), - hasType("text/plain"), - hasExtra(is(Intent.EXTRA_TEXT), startsWith("----")), - hasExtra(is(Intent.EXTRA_STREAM), - allOf(hasScheme("content"), hasHost(TemporaryFileProvider.AUTHORITY))) - )) - )).respondWith(new ActivityResult(Activity.RESULT_OK, null)); - onView(withId(R.id.view_key_action_key_share)).perform(click()); - - } - - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java index 23b3380a2..60e75bafe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.operations; import android.content.Context; -import android.net.Uri; import android.support.annotation.NonNull; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; @@ -29,7 +28,6 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -59,8 +57,7 @@ public class RevokeOperation extends BaseReadWriteOperation try { - Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId); - CachedPublicKeyRing keyRing = mKeyRepository.getCachedPublicKeyRing(secretUri); + CachedPublicKeyRing keyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId); // check if this is a master secret key we can work with switch (keyRing.getSecretKeyType(masterKeyId)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 6a5881e79..3fc54ef14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -71,11 +71,9 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmPr import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequireAnyDecryptPassphraseBuilder; @@ -638,10 +636,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation encryptSubKeyIds = keyRing.getEncryptIds(); for (Long subKeyId : encryptSubKeyIds) { CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java index 6d1313451..43dabc258 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java @@ -40,7 +40,6 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSigningAlgori import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import timber.log.Timber; @@ -160,9 +159,11 @@ class PgpSignatureChecker { for (int i = 0; i < sigList.size(); ++i) { try { long sigKeyId = sigList.get(i).getKeyID(); - CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) - ); + Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(sigKeyId); + if (masterKeyId == null) { + continue; + } + CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId); CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId); if ( ! keyCandidate.canSign()) { continue; @@ -183,9 +184,11 @@ class PgpSignatureChecker { for (int i = 0; i < sigList.size(); ++i) { try { long sigKeyId = sigList.get(i).getKeyID(); - CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId) - ); + Long masterKeyId = mKeyRepository.getMasterKeyIdBySubkeyId(sigKeyId); + if (masterKeyId == null) { + continue; + } + CanonicalizedPublicKeyRing signingRing = mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId); CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId); if ( ! keyCandidate.canSign()) { continue; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 9877a6d86..e6d997c92 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -32,7 +32,6 @@ import android.net.Uri; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.model.Certification; -import org.sufficientlysecure.keychain.model.CustomColumnAdapters; import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; @@ -175,11 +174,6 @@ public class KeyRepository extends AbstractDao { return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); } - public long getMasterKeyIdBySubKeyId(long subKeyId) throws NotFoundException { - return (Long) getGenericData(KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId), - KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER); - } - public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { long masterKeyId = new CachedPublicKeyRing(this, queryUri).extractOrGetMasterKeyId(); return getCachedPublicKeyRing(masterKeyId); @@ -189,59 +183,26 @@ public class KeyRepository extends AbstractDao { return new CachedPublicKeyRing(this, KeyRings.buildUnifiedKeyRingUri(id)); } - public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long id) throws NotFoundException { - return getCanonicalizedPublicKeyRing(KeyRings.buildUnifiedKeyRingUri(id)); - } - - public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri queryUri) throws NotFoundException { - Cursor cursor = contentResolver.query(queryUri, - new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED }, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - long masterKeyId = cursor.getLong(0); - long verified = cursor.getLong(1); - - byte[] publicKeyData = loadPublicKeyRingData(masterKeyId); - VerificationStatus verificationStatus = CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified); - return new CanonicalizedPublicKeyRing(publicKeyData, verificationStatus); - } else { - throw new NotFoundException("Key not found!"); - } - } finally { - if (cursor != null) { - cursor.close(); - } + public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long masterKeyId) throws NotFoundException { + UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId); + if (unifiedKeyInfo == null) { + throw new NotFoundException(); } + + byte[] publicKeyData = loadPublicKeyRingData(masterKeyId); + return new CanonicalizedPublicKeyRing(publicKeyData, unifiedKeyInfo.verified()); } - public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long id) throws NotFoundException { - return getCanonicalizedSecretKeyRing(KeyRings.buildUnifiedKeyRingUri(id)); - } - - public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri queryUri) throws NotFoundException { - Cursor cursor = contentResolver.query(queryUri, - new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED, KeyRings.HAS_ANY_SECRET }, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - long masterKeyId = cursor.getLong(0); - long verified = cursor.getLong(1); - int hasAnySecret = cursor.getInt(2); - if (hasAnySecret == 0) { - throw new NotFoundException("No secret key available or unknown public key!"); - } - - VerificationStatus verificationStatus = CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified); - - byte[] secretKeyData = loadSecretKeyRingData(masterKeyId); - return new CanonicalizedSecretKeyRing(secretKeyData, verificationStatus); - } else { - throw new NotFoundException("Key not found!"); - } - } finally { - if (cursor != null) { - cursor.close(); - } + public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId) throws NotFoundException { + UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId); + if (unifiedKeyInfo == null || !unifiedKeyInfo.has_any_secret()) { + throw new NotFoundException(); } + byte[] secretKeyData = loadSecretKeyRingData(masterKeyId); + if (secretKeyData == null) { + throw new IllegalStateException("Missing expected secret key data!"); + } + return new CanonicalizedSecretKeyRing(secretKeyData, unifiedKeyInfo.verified()); } public List getAllMasterKeyIds() { @@ -259,6 +220,16 @@ public class KeyRepository extends AbstractDao { return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); } + public Long getMasterKeyIdBySubkeyId(long subKeyId) { + SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId); + try (Cursor cursor = getReadableDb().query(query)) { + if (cursor.moveToFirst()) { + return SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper().map(cursor); + } + return null; + } + } + public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId); try (Cursor cursor = getReadableDb().query(query)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 3591df50f..2f29fc281 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -627,10 +627,7 @@ public class OpenPgpService extends Service { } try { - // try to find key, throws NotFoundException if not in db! - CanonicalizedPublicKeyRing keyRing = - mKeyRepository.getCanonicalizedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(masterKeyId)); + CanonicalizedPublicKeyRing keyRing = mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId); Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java index 1a8e6e2c9..0644f6e95 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java @@ -33,7 +33,6 @@ import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.remote.ApiPermissionHelper; import org.sufficientlysecure.keychain.remote.ApiPermissionHelper.WrongPackageCertificateException; import timber.log.Timber; @@ -118,9 +117,11 @@ class RequestKeyPermissionPresenter { CachedPublicKeyRing publicFallbackRing = null; for (long candidateSubKeyId : subKeyIds) { try { - CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(candidateSubKeyId) - ); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(candidateSubKeyId); + if (masterKeyId == null) { + continue; + } + CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId); SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(candidateSubKeyId); if (secretKeyType.isUsable()) { @@ -129,7 +130,7 @@ class RequestKeyPermissionPresenter { if (publicFallbackRing == null) { publicFallbackRing = cachedPublicKeyRing; } - } catch (PgpKeyNotFoundException | NotFoundException e) { + } catch (NotFoundException e) { // no matter } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 8387ff951..bcba0af9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -29,7 +29,6 @@ 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.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -41,14 +40,12 @@ import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -193,18 +190,14 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. } private void showKey(long keyId) { - try { - - KeyRepository keyRepository = KeyRepository.create(requireContext()); - long masterKeyId = keyRepository.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId) - ).getMasterKeyId(); - Intent viewKeyIntent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), masterKeyId); - startActivity(viewKeyIntent); - - } catch (PgpKeyNotFoundException e) { + KeyRepository keyRepository = KeyRepository.create(requireContext()); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(keyId); + if (masterKeyId == null) { Notify.create(getActivity(), R.string.error_key_not_found, Style.ERROR).show(); + return; } + Intent viewKeyIntent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), masterKeyId); + startActivity(viewKeyIntent); } protected void loadVerifyResult(DecryptVerifyResult decryptVerifyResult) { 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 3620063e4..dd851052e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -139,8 +138,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) { if (signatureKeyId != null) { try { - CachedPublicKeyRing keyring = mKeyRepository.getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingUri(signatureKeyId)); + CachedPublicKeyRing keyring = mKeyRepository.getCachedPublicKeyRing(signatureKeyId); if (keyring.hasAnySecret()) { mSignKeySpinner.setPreSelectedKeyId(signatureKeyId); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index bd0f84764..4e48100ed 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -285,10 +285,9 @@ public class PassphraseDialogActivity extends FragmentActivity { } else { long subKeyId = subKeyIds[0]; - KeyRepository helper = - KeyRepository.create(getContext()); - CachedPublicKeyRing cachedPublicKeyRing = helper.getCachedPublicKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + KeyRepository keyRepository = KeyRepository.create(getContext()); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(subKeyId); + CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId); // yes the inner try/catch block is necessary, otherwise the final variable // above can't be statically verified to have been set in all cases because // the catch clause doesn't return. @@ -534,11 +533,13 @@ public class PassphraseDialogActivity extends FragmentActivity { CanonicalizedSecretKey canonicalizedSecretKey = null; for (long subKeyId : mRequiredInput.getSubKeyIds()) { - CanonicalizedSecretKeyRing secretKeyRing = - KeyRepository.create(getContext()).getCanonicalizedSecretKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); - CanonicalizedSecretKey secretKeyToUnlock = - secretKeyRing.getSecretKey(subKeyId); + KeyRepository keyRepository = KeyRepository.create(getContext()); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(subKeyId); + if (masterKeyId == null) { + continue; + } + CanonicalizedSecretKeyRing secretKeyRing = keyRepository.getCanonicalizedSecretKeyRing(masterKeyId); + CanonicalizedSecretKey secretKeyToUnlock = secretKeyRing.getSecretKey(subKeyId); // this is the operation may take a very long time (100ms to several seconds!) boolean unlockSucceeded = secretKeyToUnlock.unlock(passphrase); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 496b4adfd..74c17ea5b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -199,12 +199,11 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { throw new IOException(getString(R.string.error_wrong_security_token)); } - KeyRepository keyRepository = - KeyRepository.create(this); + KeyRepository keyRepository = KeyRepository.create(this); CanonicalizedPublicKeyRing publicKeyRing; try { - publicKeyRing = keyRepository.getCanonicalizedPublicKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId())); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(mRequiredInput.getMasterKeyId()); + publicKeyRing = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId); } catch (KeyRepository.NotFoundException e) { throw new IOException("Couldn't find subkey for key to token operation."); } @@ -263,9 +262,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { KeyRepository.create(this); CanonicalizedSecretKeyRing secretKeyRing; try { - secretKeyRing = keyRepository.getCanonicalizedSecretKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId()) - ); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(mRequiredInput.getMasterKeyId()); + secretKeyRing = keyRepository.getCanonicalizedSecretKeyRing(masterKeyId); } catch (KeyRepository.NotFoundException e) { throw new IOException("Couldn't find subkey for key to token operation."); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java index 3d9170064..a0be35c87 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java @@ -115,11 +115,13 @@ public abstract class PublicKeyRetrievalLoader extends AsyncTaskLoader Date: Mon, 25 Jun 2018 18:04:04 +0200 Subject: [PATCH 046/124] use LiveData to load key data in DecryptFragment --- .../keychain/ui/DecryptFragment.java | 147 ++++++------------ .../keychain/ui/DisplayTextFragment.java | 3 +- 2 files changed, 49 insertions(+), 101 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index bcba0af9b..f0fdb33c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -21,16 +21,12 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import android.app.Activity; +import android.arch.lifecycle.LiveData; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; 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.View; -import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; @@ -43,12 +39,13 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; @@ -60,9 +57,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; -public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks { - - public static final int LOADER_ID_UNIFIED = 0; +public abstract class DecryptFragment extends Fragment { public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result"; protected LinearLayout mResultLayout; @@ -80,32 +75,29 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. private ViewAnimator mOverlayAnimator; private CryptoOperationHelper mImportOpHelper; + private LiveData unifiedKeyInfoLiveData; @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + Activity activity = requireActivity(); // NOTE: These views are inside the activity! - mResultLayout = getActivity().findViewById(R.id.result_main_layout); + mResultLayout = activity.findViewById(R.id.result_main_layout); + mEncryptionIcon = activity.findViewById(R.id.result_encryption_icon); + mEncryptionText = activity.findViewById(R.id.result_encryption_text); + mSignatureIcon = activity.findViewById(R.id.result_signature_icon); + mSignatureText = activity.findViewById(R.id.result_signature_text); + mSignatureLayout = activity.findViewById(R.id.result_signature_layout); + mSignatureName = activity.findViewById(R.id.result_signature_name); + mSignatureEmail = activity.findViewById(R.id.result_signature_email); + mSignatureAction = activity.findViewById(R.id.result_signature_action); mResultLayout.setVisibility(View.GONE); - mEncryptionIcon = getActivity().findViewById(R.id.result_encryption_icon); - mEncryptionText = getActivity().findViewById(R.id.result_encryption_text); - mSignatureIcon = getActivity().findViewById(R.id.result_signature_icon); - mSignatureText = getActivity().findViewById(R.id.result_signature_text); - mSignatureLayout = getActivity().findViewById(R.id.result_signature_layout); - mSignatureName = getActivity().findViewById(R.id.result_signature_name); - mSignatureEmail = getActivity().findViewById(R.id.result_signature_email); - mSignatureAction = getActivity().findViewById(R.id.result_signature_action); // Overlay mOverlayAnimator = (ViewAnimator) view; Button vErrorOverlayButton = view.findViewById(R.id.decrypt_error_overlay_button); - vErrorOverlayButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mOverlayAnimator.setDisplayedChild(0); - } - }); + vErrorOverlayButton.setOnClickListener(v -> mOverlayAnimator.setDisplayedChild(0)); } private void showErrorOverlay(boolean overlay) { @@ -116,7 +108,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(ARG_DECRYPT_VERIFY_RESULT, mDecryptVerifyResult); @@ -165,7 +157,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. public void onCryptoOperationSuccess(ImportKeyResult result) { result.createNotify(getActivity()).show(); - getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); + loadSignerKeyData(); } @Override @@ -237,16 +229,13 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. mSignatureText.setText(R.string.decrypt_result_no_signature); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.NOT_SIGNED); - getLoaderManager().destroyLoader(LOADER_ID_UNIFIED); - + loadSignerKeyData(); showErrorOverlay(false); onVerifyLoaded(true); } else { // signature present - - // after loader is restarted signature results are checked - getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this); + loadSignerKeyData(); } } @@ -257,70 +246,43 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. private void setShowAction(final long signatureKeyId) { mSignatureAction.setText(R.string.decrypt_result_action_show); mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_vpn_key_grey_24dp, 0); - mSignatureLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showKey(signatureKeyId); - } - }); + mSignatureLayout.setOnClickListener(v -> showKey(signatureKeyId)); } - // These are the rows that we will retrieve. - static final String[] UNIFIED_PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT, - }; - - @SuppressWarnings("unused") - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_USER_ID = 2; - static final int INDEX_VERIFIED = 3; - static final int INDEX_HAS_ANY_SECRET = 4; - static final int INDEX_NAME = 5; - static final int INDEX_EMAIL = 6; - static final int INDEX_COMMENT = 7; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - if (id != LOADER_ID_UNIFIED) { - return null; + public void loadSignerKeyData() { + if (unifiedKeyInfoLiveData != null) { + unifiedKeyInfoLiveData.removeObservers(this); + unifiedKeyInfoLiveData = null; } - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( - mSignatureResult.getKeyId()); - return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - - if (loader.getId() != LOADER_ID_UNIFIED) { + if (mSignatureResult == null || mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_NO_SIGNATURE) { + setSignatureLayoutVisibility(View.GONE); return; } - // If the key is unknown, show it as such - if (data.getCount() == 0 || !data.moveToFirst()) { + unifiedKeyInfoLiveData = new GenericLiveData<>(requireContext(), null, () -> { + KeyRepository keyRepository = KeyRepository.create(requireContext()); + Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(mSignatureResult.getKeyId()); + return keyRepository.getUnifiedKeyInfo(masterKeyId); + }); + unifiedKeyInfoLiveData.observe(this, this::onLoadSignerKeyData); + } + + public void onLoadSignerKeyData(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { showUnknownKeyStatus(); return; } long signatureKeyId = mSignatureResult.getKeyId(); - String name = data.getString(INDEX_NAME); - String email = data.getString(INDEX_EMAIL); - if (name != null) { - mSignatureName.setText(name); + if (unifiedKeyInfo.name() != null) { + mSignatureName.setText(unifiedKeyInfo.name()); } else { mSignatureName.setText(R.string.user_id_no_name); } - if (email != null) { - mSignatureEmail.setText(email); + if (unifiedKeyInfo.email() != null) { + mSignatureEmail.setText(unifiedKeyInfo.email()); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( mSignatureResult.getKeyId())); @@ -331,8 +293,8 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. boolean isRevoked = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED; boolean isExpired = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED; boolean isInsecure = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE; - boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; - boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + boolean isVerified = unifiedKeyInfo.verified() == VerificationStatus.VERIFIED_SECRET; + boolean isYours = unifiedKeyInfo.has_any_secret(); if (isRevoked) { mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); @@ -402,16 +364,6 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. } - @Override - public void onLoaderReset(Loader loader) { - - if (loader.getId() != LOADER_ID_UNIFIED) { - return; - } - - setSignatureLayoutVisibility(View.GONE); - } - private void showUnknownKeyStatus() { final long signatureKeyId = mSignatureResult.getKeyId(); @@ -446,12 +398,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. mSignatureAction.setText(R.string.decrypt_result_action_Lookup); mSignatureAction .setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_file_download_grey_24dp, 0); - mSignatureLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - lookupUnknownKey(signatureKeyId); - } - }); + mSignatureLayout.setOnClickListener(v -> lookupUnknownKey(signatureKeyId)); showErrorOverlay(false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index 5102d74fc..eb1c2e0ea 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -23,6 +23,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.util.Linkify; import android.view.LayoutInflater; @@ -100,7 +101,7 @@ public class DisplayTextFragment extends DecryptFragment { } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Bundle args = getArguments(); From aa640f32275e3e3b4d7c8d251d7d61caca301aac Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 25 Jun 2018 18:11:26 +0200 Subject: [PATCH 047/124] Small layout fix to key list item --- OpenKeychain/src/main/res/layout/key_list_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/res/layout/key_list_item.xml b/OpenKeychain/src/main/res/layout/key_list_item.xml index 0a1609b24..f376ffedc 100644 --- a/OpenKeychain/src/main/res/layout/key_list_item.xml +++ b/OpenKeychain/src/main/res/layout/key_list_item.xml @@ -18,6 +18,7 @@ android:layout_height="24dp" android:layout_margin="4dp" android:id="@+id/key_list_item_tid_icon" + android:visibility="gone" tools:src="@drawable/apps_k9" /> Date: Tue, 26 Jun 2018 10:24:19 +0200 Subject: [PATCH 048/124] extract database access from CachedPublicKeyRing --- .../keychain/operations/BackupOperation.java | 69 ++--- .../keychain/operations/CertifyOperation.java | 4 +- .../keychain/operations/RevokeOperation.java | 16 +- .../pgp/OpenPgpSignatureResultBuilder.java | 2 +- .../pgp/PgpDecryptVerifyOperation.java | 5 +- .../keychain/pgp/PgpSignEncryptOperation.java | 9 +- .../keychain/provider/AbstractDao.java | 9 + .../provider/CachedPublicKeyRing.java | 235 +++--------------- .../keychain/provider/KeyRepository.java | 132 ++++------ .../provider/KeyWritableRepository.java | 46 +--- .../keychain/provider/KeychainContract.java | 13 - .../keychain/provider/KeychainDatabase.java | 15 +- .../keychain/provider/KeychainProvider.java | 39 +-- .../keychain/remote/OpenPgpService.java | 14 +- .../remote/SshAuthenticationService.java | 21 +- .../ui/RequestKeyPermissionPresenter.java | 2 +- .../service/PassphraseCacheService.java | 9 +- .../keychain/ssh/AuthenticationOperation.java | 16 +- .../keychain/ui/CertifyKeyFragment.java | 4 +- .../keychain/ui/CreateKeyFinalFragment.java | 6 +- .../keychain/ui/DeleteKeyDialogActivity.java | 89 +++---- .../ui/EncryptModeAsymmetricFragment.java | 3 +- .../keychain/ui/PassphraseDialogActivity.java | 10 +- .../ui/keyview/LinkedIdViewFragment.java | 7 +- .../keychain/ui/keyview/ViewKeyActivity.java | 3 +- .../ui/token/PublicKeyRetrievalLoader.java | 8 +- .../transfer/presenter/TransferPresenter.java | 2 +- .../org/sufficientlysecure/keychain/Certs.sq | 2 +- .../org/sufficientlysecure/keychain/Keys.sq | 27 +- .../keychain/KeychainTestRunner.java | 1 + .../AuthenticationOperationTest.java | 24 +- .../operations/BackupOperationTest.java | 21 +- .../operations/CertifyOperationTest.java | 20 +- .../operations/PromoteKeyOperationTest.java | 8 +- .../keychain/pgp/PgpEncryptDecryptTest.java | 3 +- .../keychain/pgp/PgpKeyOperationTest.java | 19 +- .../pgp/UncachedKeyringMergeTest.java | 13 +- .../keychain/provider/InteropTest.java | 108 ++------ .../provider/KeyRepositorySaveTest.java | 22 +- .../keychain/ssh/SshPublicKeyTest.java | 2 +- 40 files changed, 328 insertions(+), 730 deletions(-) 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 7bb28029d..65bbd4d7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java @@ -24,24 +24,21 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import android.content.Context; -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; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -56,14 +53,13 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil; import org.sufficientlysecure.keychain.util.CountingOutputStream; import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil; import org.sufficientlysecure.keychain.util.Passphrase; import timber.log.Timber; @@ -84,8 +80,6 @@ public class BackupOperation extends BaseOperation { KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET }; - private static final int INDEX_MASTER_KEY_ID = 0; - private static final int INDEX_HAS_ANY_SECRET = 1; // this is a very simple matcher, we only need basic sanitization private static final Pattern HEADER_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+: [^\\n]+"); @@ -224,42 +218,36 @@ public class BackupOperation extends BaseOperation { OutputStream outStream, List extraSecretKeyHeaders) { // noinspection unused TODO use these in a log entry int okSecret = 0, okPublic = 0; - int progress = 0; - Cursor cursor = queryForKeys(masterKeyIds); - - if (cursor == null || !cursor.moveToFirst()) { - log.add(LogType.MSG_BACKUP_ERROR_DB, 1); - return false; // new ExportResult(ExportResult.RESULT_ERROR, log); - } - try { - - int numKeys = cursor.getCount(); + List unifiedKeyInfos; + if (masterKeyIds == null) { + unifiedKeyInfos = mKeyRepository.getAllUnifiedKeyInfo(); + } else { + unifiedKeyInfos = mKeyRepository.getUnifiedKeyInfo(masterKeyIds); + } + int numKeys = unifiedKeyInfos.size(); updateProgress(mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, numKeys), 0, numKeys); // For each public masterKey id - while (!cursor.isAfterLast()) { - - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - log.add(LogType.MSG_BACKUP_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(masterKeyId)); + for (UnifiedKeyInfo keyInfo : unifiedKeyInfos) { + log.add(LogType.MSG_BACKUP_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyInfo.master_key_id())); boolean publicKeyWriteOk = false; if (exportPublic) { - publicKeyWriteOk = writePublicKeyToStream(masterKeyId, log, outStream); + publicKeyWriteOk = writePublicKeyToStream(keyInfo.master_key_id(), log, outStream); if (publicKeyWriteOk) { okPublic += 1; } } if (publicKeyWriteOk || !exportPublic) { - boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0; - if (exportSecret && hasSecret) { - log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(masterKeyId)); - if (writeSecretKeyToStream(masterKeyId, log, outStream, extraSecretKeyHeaders)) { + if (exportSecret && keyInfo.has_any_secret()) { + log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyInfo.master_key_id())); + if (writeSecretKeyToStream(keyInfo.master_key_id(), log, outStream, extraSecretKeyHeaders)) { okSecret += 1; } extraSecretKeyHeaders = null; @@ -267,7 +255,6 @@ public class BackupOperation extends BaseOperation { } updateProgress(progress++, numKeys); - cursor.moveToNext(); } updateProgress(R.string.progress_done, numKeys, numKeys); @@ -282,7 +269,6 @@ public class BackupOperation extends BaseOperation { } catch (Exception e) { Timber.e(e, "error closing stream"); } - cursor.close(); } return true; @@ -342,29 +328,4 @@ public class BackupOperation extends BaseOperation { } } - private Cursor queryForKeys(long[] masterKeyIds) { - String selection = null, selectionArgs[] = null; - - if (masterKeyIds != null) { - // convert long[] to String[] - selectionArgs = new String[masterKeyIds.length]; - for (int i = 0; i < masterKeyIds.length; i++) { - selectionArgs[i] = Long.toString(masterKeyIds[i]); - } - - // generates ?,?,? as placeholders for selectionArgs - String placeholders = TextUtils.join(",", - Collections.nCopies(masterKeyIds.length, "?")); - - // put together selection string - selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID - + " IN (" + placeholders + ")"; - } - - return mKeyRepository.getContentResolver().query( - KeyRings.buildUnifiedKeyRingsUri(), PROJECTION, selection, selectionArgs, - Tables.KEYS + "." + KeyRings.MASTER_KEY_ID - ); - } - } \ No newline at end of file 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 f6981d33a..ced8173cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -86,10 +86,8 @@ public class CertifyOperation extends BaseReadWriteOperation KeyFormattingUtils.beautifyKeyId(masterKeyId)); try { - - CachedPublicKeyRing keyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId); + UnifiedKeyInfo keyInfo = mKeyRepository.getUnifiedKeyInfo(masterKeyId); + if (keyInfo == null) { + log.add(OperationResult.LogType.MSG_REVOKE_ERROR_KEY_FAIL, 1); + return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); + } // check if this is a master secret key we can work with - switch (keyRing.getSecretKeyType(masterKeyId)) { + switch (mKeyRepository.getSecretKeyType(masterKeyId)) { case GNU_DUMMY: log.add(OperationResult.LogType.MSG_EK_ERROR_DUMMY, 1); return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); } SaveKeyringParcel.Builder saveKeyringParcel = - SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, keyRing.getFingerprint()); + SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, keyInfo.fingerprint()); // all revoke operations are made atomic as of now saveKeyringParcel.setUpdateOptions(revokeKeyringParcel.isShouldUpload(), true, @@ -93,7 +95,7 @@ public class RevokeOperation extends BaseReadWriteOperation return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); } - } catch (PgpKeyNotFoundException | KeyWritableRepository.NotFoundException e) { + } catch (KeyWritableRepository.NotFoundException e) { Timber.e(e, "could not find key to revoke"); log.add(OperationResult.LogType.MSG_REVOKE_ERROR_KEY_FAIL, 1); return new RevokeResult(RevokeResult.RESULT_ERROR, log, masterKeyId); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 6ba5d4414..399b0152c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -124,7 +124,7 @@ public class OpenPgpSignatureResultBuilder { } catch (PgpKeyNotFoundException e) { Timber.d("No primary user id in keyring with master key id " + signingRing.getMasterKeyId()); } - setSignatureKeyCertified(signingRing.getVerified() != VerificationStatus.UNVERIFIED); + setSignatureKeyCertified(signingRing.getVerified() == VerificationStatus.VERIFIED_SECRET); List allUserIds = signingRing.getUnorderedUserIds(); List confirmedUserIds = mKeyRepository.getConfirmedUserIds(signingRing.getMasterKeyId()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 3fc54ef14..57220f9e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -71,7 +71,6 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmPr import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -633,7 +632,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation T mapSingleRow(SupportSQLiteQuery query, Mapper mapper) { + try (Cursor cursor = getReadableDb().query(query)) { + if (cursor.moveToNext()) { + return mapper.map(cursor); + } + } + return null; + } + interface Mapper { T map(Cursor cursor); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index ee20d5e6c..425dc587d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -18,16 +18,11 @@ package org.sufficientlysecure.keychain.provider; -import android.net.Uri; - -import org.sufficientlysecure.keychain.model.CustomColumnAdapters; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import timber.log.Timber; /** This implementation of KeyRing provides a cached view of PublicKeyRing @@ -47,234 +42,64 @@ import timber.log.Timber; * */ public class CachedPublicKeyRing extends KeyRing { + private UnifiedKeyInfo unifiedKeyInfo; - final KeyRepository mKeyRepository; - final Uri mUri; - - public CachedPublicKeyRing(KeyRepository keyRepository, Uri uri) { - mKeyRepository = keyRepository; - mUri = uri; + public CachedPublicKeyRing(UnifiedKeyInfo unifiedKeyInfo) { + this.unifiedKeyInfo = unifiedKeyInfo; } @Override - public long getMasterKeyId() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.MASTER_KEY_ID, KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data; - } catch (KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public long getMasterKeyId() { + return unifiedKeyInfo.master_key_id(); } - /** - * Find the master key id related to a given query. The id will either be extracted from the - * query, which should work for all specific /key_rings/ queries, or will be queried if it can't. - */ - public long extractOrGetMasterKeyId() throws PgpKeyNotFoundException { - // try extracting from the uri first - String firstSegment = mUri.getPathSegments().get(1); - if (!"find".equals(firstSegment)) try { - return Long.parseLong(firstSegment); - } catch (NumberFormatException e) { - // didn't work? oh well. - Timber.d("Couldn't get masterKeyId from URI, querying..."); - } - return getMasterKeyId(); + public byte[] getFingerprint() { + return unifiedKeyInfo.fingerprint(); } - public byte[] getFingerprint() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.FINGERPRINT, KeyRepository.FIELD_TYPE_BLOB); - return (byte[]) data; - } catch (KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - public long getCreationTime() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.CREATION, KeyRepository.FIELD_TYPE_INTEGER); - return (long) data; - } catch (KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public long getCreationTime() { + return unifiedKeyInfo.creation(); } @Override - public String getPrimaryUserId() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.USER_ID, - KeyRepository.FIELD_TYPE_STRING); - return (String) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public String getPrimaryUserId() { + return unifiedKeyInfo.user_id(); } - public String getPrimaryUserIdWithFallback() throws PgpKeyNotFoundException { + public String getPrimaryUserIdWithFallback() { return getPrimaryUserId(); } - public String getName() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.NAME, - KeyRepository.FIELD_TYPE_STRING); - return (String) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - public String getEmail() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.EMAIL, - KeyRepository.FIELD_TYPE_STRING); - return (String) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - - public String getComment() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.COMMENT, - KeyRepository.FIELD_TYPE_STRING); - return (String) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + @Override + public boolean isRevoked() { + return unifiedKeyInfo.is_revoked(); } @Override - public boolean isRevoked() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.IS_REVOKED, - KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data > 0; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public boolean canCertify() { + return unifiedKeyInfo.can_certify(); } @Override - public boolean canCertify() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.HAS_CERTIFY_SECRET, - KeyRepository.FIELD_TYPE_NULL); - return !((Boolean) data); - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public long getEncryptId() { + return unifiedKeyInfo.has_encrypt_key_int(); } @Override - public long getEncryptId() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.HAS_ENCRYPT, - KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public boolean hasEncrypt() { + return unifiedKeyInfo.has_encrypt_key(); + } + + public long getAuthenticationId() { + return unifiedKeyInfo.has_auth_key_int(); } @Override - public boolean hasEncrypt() throws PgpKeyNotFoundException { - return getEncryptId() != 0; + public VerificationStatus getVerified() { + return unifiedKeyInfo.verified(); } - /** Returns the key id which should be used for signing. - * - * This method returns keys which are actually available (ie. secret available, and not stripped, - * revoked, or expired), hence only works on keyrings where a secret key is available! - * - */ - public long getSecretSignId() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.HAS_SIGN_SECRET, - KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - /** Returns the key id which should be used for authentication. - * - * This method returns keys which are actually available (ie. secret available, and not stripped, - * revoked, or expired), hence only works on keyrings where a secret key is available! - * - */ - public long getSecretAuthenticationId() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.HAS_AUTHENTICATE_SECRET, - KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - public long getAuthenticationId() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeyRings.HAS_AUTHENTICATE, - KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - @Override - public VerificationStatus getVerified() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.VERIFIED, - KeyRepository.FIELD_TYPE_INTEGER); - return CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode((Long) data); - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - public boolean hasAnySecret() throws PgpKeyNotFoundException { - try { - Object data = mKeyRepository.getGenericData(mUri, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeyRepository.FIELD_TYPE_INTEGER); - return (Long) data > 0; - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } - } - - public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { - SecretKeyType secretKeyType = mKeyRepository.getSecretKeyType(keyId); - if (secretKeyType == null) { - throw new NotFoundException(); - } - return secretKeyType; - } - - public byte[] getEncoded() throws PgpKeyNotFoundException { - try { - return mKeyRepository.loadPublicKeyRingData(getMasterKeyId()); - } catch(KeyWritableRepository.NotFoundException e) { - throw new PgpKeyNotFoundException(e); - } + public boolean hasAnySecret() { + return unifiedKeyInfo.has_any_secret(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index e6d997c92..4c60c0c67 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -21,13 +21,11 @@ package org.sufficientlysecure.keychain.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import android.net.Uri; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; @@ -43,21 +41,10 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStat import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import timber.log.Timber; public class KeyRepository extends AbstractDao { - // If we ever switch to api level 11, we can ditch this whole mess! - public static final int FIELD_TYPE_NULL = 1; - // this is called integer to stay coherent with the constants in Cursor (api level 11) - public static final int FIELD_TYPE_INTEGER = 2; - public static final int FIELD_TYPE_FLOAT = 3; - public static final int FIELD_TYPE_STRING = 4; - public static final int FIELD_TYPE_BLOB = 5; - final ContentResolver contentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; final LocalSecretKeyStorage localSecretKeyStorage; @@ -115,72 +102,13 @@ public class KeyRepository extends AbstractDao { mLog = new OperationLog(); } - Object getGenericData(Uri uri, String column, int type) throws NotFoundException { - Object result = getGenericData(uri, new String[]{column}, new int[]{type}, null).get(column); - if (result == null) { + // replace with getUnifiedKeyInfo + public CachedPublicKeyRing getCachedPublicKeyRing(long masterKeyId) throws NotFoundException { + UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId); + if (unifiedKeyInfo == null) { throw new NotFoundException(); } - return result; - } - - private HashMap getGenericData(Uri uri, String[] proj, int[] types) - throws NotFoundException { - return getGenericData(uri, proj, types, null); - } - - private HashMap getGenericData(Uri uri, String[] proj, int[] types, String selection) - throws NotFoundException { - Cursor cursor = contentResolver.query(uri, proj, selection, null, null); - - try { - HashMap result = new HashMap<>(proj.length); - if (cursor != null && cursor.moveToFirst()) { - int pos = 0; - for (String p : proj) { - switch (types[pos]) { - case FIELD_TYPE_NULL: - result.put(p, cursor.isNull(pos)); - break; - case FIELD_TYPE_INTEGER: - result.put(p, cursor.getLong(pos)); - break; - case FIELD_TYPE_FLOAT: - result.put(p, cursor.getFloat(pos)); - break; - case FIELD_TYPE_STRING: - result.put(p, cursor.getString(pos)); - break; - case FIELD_TYPE_BLOB: - result.put(p, cursor.getBlob(pos)); - break; - } - pos += 1; - } - } else { - // If no data was found, throw an appropriate exception - throw new NotFoundException(); - } - - return result; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - public HashMap getUnifiedData(long masterKeyId, String[] proj, int[] types) - throws NotFoundException { - return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); - } - - public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { - long masterKeyId = new CachedPublicKeyRing(this, queryUri).extractOrGetMasterKeyId(); - return getCachedPublicKeyRing(masterKeyId); - } - - public CachedPublicKeyRing getCachedPublicKeyRing(long id) { - return new CachedPublicKeyRing(this, KeyRings.buildUnifiedKeyRingUri(id)); + return new CachedPublicKeyRing(unifiedKeyInfo); } public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long masterKeyId) throws NotFoundException { @@ -211,11 +139,7 @@ public class KeyRepository extends AbstractDao { } public List getMasterKeyIdsBySigner(List signerMasterKeyIds) { - long[] signerKeyIds = new long[signerMasterKeyIds.size()]; - int i = 0; - for (Long signerKeyId : signerMasterKeyIds) { - signerKeyIds[i++] = signerKeyId; - } + long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds); SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); } @@ -240,6 +164,11 @@ public class KeyRepository extends AbstractDao { } } + public List getUnifiedKeyInfo(long... masterKeyIds) { + SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds); + return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); + } + public List getUnifiedKeyInfosByMailAddress(String mailAddress) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%'); return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); @@ -275,17 +204,17 @@ public class KeyRepository extends AbstractDao { return mapAllRows(query, SubKey.SUBKEY_MAPPER::map); } - public SecretKeyType getSecretKeyType(long keyId) { + public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId); try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToFirst()) { return SubKey.SKT_MAPPER.map(cursor); } - return null; + throw new NotFoundException(); } } - private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException { + private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ArmoredOutputStream aos = new ArmoredOutputStream(bos); @@ -295,15 +224,13 @@ public class KeyRepository extends AbstractDao { return bos.toByteArray(); } - public String getPublicKeyRingAsArmoredString(long masterKeyId) - throws NotFoundException, IOException, PgpGeneralException { + public String getPublicKeyRingAsArmoredString(long masterKeyId) throws NotFoundException, IOException { byte[] data = loadPublicKeyRingData(masterKeyId); byte[] armoredData = getKeyRingAsArmoredData(data); return new String(armoredData); } - public byte[] getSecretKeyRingAsArmoredData(long masterKeyId) - throws NotFoundException, IOException, PgpGeneralException { + public byte[] getSecretKeyRingAsArmoredData(long masterKeyId) throws NotFoundException, IOException { byte[] data = loadSecretKeyRingData(masterKeyId); return getKeyRingAsArmoredData(data); } @@ -338,6 +265,24 @@ public class KeyRepository extends AbstractDao { } } + public long getSecretSignId(long masterKeyId) throws NotFoundException { + SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId); + Long signKeyId = mapSingleRow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map); + if (signKeyId == null) { + throw new NotFoundException(); + } + return signKeyId; + } + + public Long getSecretAuthenticationId(long masterKeyId) throws NotFoundException { + SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId); + Long authKeyId = mapSingleRow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map); + if (authKeyId == null) { + throw new NotFoundException(); + } + return authKeyId; + } + public static class NotFoundException extends Exception { public NotFoundException() { } @@ -346,4 +291,13 @@ public class KeyRepository extends AbstractDao { super(name); } } + + private long[] getLongListAsArray(List longList) { + long[] longs = new long[longList.size()]; + int i = 0; + for (Long aLong : longList) { + longs[i++] = aLong; + } + return longs; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 8d42d78cf..0a6786d76 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -29,7 +29,6 @@ import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; -import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; import android.support.annotation.NonNull; @@ -40,6 +39,7 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.CustomColumnAdapters; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; @@ -60,7 +60,6 @@ import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; @@ -99,8 +98,7 @@ public class KeyWritableRepository extends KeyRepository { localPublicKeyStorage, localSecretKeyStorage, databaseNotifyManager, autocryptPeerDao); } - @VisibleForTesting - KeyWritableRepository(Context context, + private KeyWritableRepository(Context context, KeychainDatabase database, LocalPublicKeyStorage localPublicKeyStorage, LocalSecretKeyStorage localSecretKeyStorage, DatabaseNotifyManager databaseNotifyManager, AutocryptPeerDao autocryptPeerDao) { @@ -120,40 +118,22 @@ public class KeyWritableRepository extends KeyRepository { } private LongSparseArray getTrustedMasterKeys() { - Cursor cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] { - KeyRings.MASTER_KEY_ID, - // we pick from cache only information that is not easily available from keyrings - KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED - }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); + LongSparseArray result = new LongSparseArray<>(); - try { - LongSparseArray result = new LongSparseArray<>(); - - if (cursor == null) { - return result; - } - - while (cursor.moveToNext()) { - try { - long masterKeyId = cursor.getLong(0); - long verified = cursor.getLong(2); - byte[] blob = loadPublicKeyRingData(masterKeyId); - VerificationStatus verificationStatus = CustomColumnAdapters.VERIFICATON_STATUS_ADAPTER.decode(verified); - if (blob != null) { - result.put(masterKeyId, new CanonicalizedPublicKeyRing(blob, verificationStatus).getPublicKey()); - } - } catch (NotFoundException e) { - throw new IllegalStateException("Error reading secret key data, this should not happen!", e); + List unifiedKeyInfoWithSecret = getAllUnifiedKeyInfoWithSecret(); + for (UnifiedKeyInfo unifiedKeyInfo : unifiedKeyInfoWithSecret) { + try { + byte[] blob = loadPublicKeyRingData(unifiedKeyInfo.master_key_id()); + if (blob != null) { + result.put(unifiedKeyInfo.master_key_id(), + new CanonicalizedPublicKeyRing(blob, unifiedKeyInfo.verified()).getPublicKey()); } - } - - return result; - } finally { - if (cursor != null) { - cursor.close(); + } catch (NotFoundException e) { + throw new IllegalStateException("Error reading secret key data, this should not happen!", e); } } + return result; } // bits, in order: CESA. make SURE these are correct, we will get bad log entries otherwise!! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index edb8ea0a9..bba2fcd58 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -99,9 +99,6 @@ public class KeychainContract { public static final String PATH_UNIFIED = "unified"; - public static final String PATH_FIND = "find"; - public static final String PATH_BY_SUBKEY = "subkey"; - public static final String PATH_PUBLIC = "public"; public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_KEYS = "keys"; @@ -132,16 +129,6 @@ public class KeychainContract { public static Uri buildGenericKeyRingUri(long masterKeyId) { return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); } - - public static Uri buildUnifiedKeyRingUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)) - .appendPath(PATH_UNIFIED).build(); - } - - public static Uri buildUnifiedKeyRingsFindBySubkeyUri(long subkey) { - return CONTENT_URI.buildUpon().appendPath(PATH_FIND) - .appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build(); - } } public static class KeyRingData implements KeyRingsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index da0cbb543..ac865bfc4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -32,7 +32,9 @@ import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteException; +import android.support.annotation.VisibleForTesting; +import org.sufficientlysecure.keychain.ApiAllowedKeysModel; import org.sufficientlysecure.keychain.ApiAppsModel; import org.sufficientlysecure.keychain.AutocryptPeersModel; import org.sufficientlysecure.keychain.CertsModel; @@ -66,12 +68,15 @@ public class KeychainDatabase { private static KeychainDatabase sInstance; public static KeychainDatabase getInstance(Context context) { - if (sInstance == null) { sInstance = new KeychainDatabase(context.getApplicationContext()); - } return sInstance; } + @VisibleForTesting + public static void resetSingleton() { + sInstance = null; + } + public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; String KEYS = "keys"; @@ -133,7 +138,7 @@ public class KeychainDatabase { db.execSQL(ApiAppsModel.CREATE_TABLE); db.execSQL(OverriddenWarningsModel.CREATE_TABLE); db.execSQL(AutocryptPeersModel.CREATE_TABLE); - db.execSQL(ApiAppsModel.CREATE_TABLE); + db.execSQL(ApiAllowedKeysModel.CREATE_TABLE); db.execSQL(KeysModel.UNIFIEDKEYVIEW); db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");"); @@ -460,8 +465,8 @@ public class KeychainDatabase { // DANGEROUS, use in test code ONLY! public void clearDatabase() { - getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC); - getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS); + getWritableDatabase().execSQL("delete from " + KeyRingsPublicModel.TABLE_NAME); + getWritableDatabase().execSQL("delete from " + ApiAllowedKeysModel.TABLE_NAME); getWritableDatabase().execSQL("delete from api_apps"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 800a3e5ad..89b0e5cbb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -61,8 +61,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RING_PUBLIC = 203; private static final int KEY_RING_CERTS = 205; - private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; - private static final int KEY_SIGNATURES = 700; protected UriMatcher mUriMatcher; @@ -88,16 +86,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe + "/" + KeychainContract.PATH_UNIFIED, KEY_RINGS_UNIFIED); - /* - * find by criteria other than master key id - * - * key_rings/find/subkey/_ - * - */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" - + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_SUBKEY + "/*", - KEY_RINGS_FIND_BY_SUBKEY); - /* * list key_ring specifics * @@ -174,8 +162,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe switch (match) { case KEY_RING_UNIFIED: - case KEY_RINGS_UNIFIED: - case KEY_RINGS_FIND_BY_SUBKEY: { + case KEY_RINGS_UNIFIED: { HashMap projectionMap = new HashMap<>(); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); @@ -318,28 +305,8 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe // in case there are multiple verifying certificates groupBy = Tables.KEYS + "." + Keys.MASTER_KEY_ID; - switch(match) { - case KEY_RING_UNIFIED: { - qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - break; - } - case KEY_RINGS_FIND_BY_SUBKEY: { - try { - String subkey = Long.valueOf(uri.getLastPathSegment()).toString(); - qb.appendWhere(" AND EXISTS (" - + " SELECT 1 FROM " + Tables.KEYS + " AS tmp" - + " WHERE tmp." + UserPackets.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND tmp." + Keys.KEY_ID + " = " + subkey + "" - + ")"); - } catch(NumberFormatException e) { - Timber.e(e, "Malformed find by subkey query!"); - qb.appendWhere(" AND 0"); - } - break; - } - } + qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 2f29fc281..c6ee125de 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -66,12 +66,11 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult; @@ -139,9 +138,9 @@ public class OpenPgpService extends Service { // get first usable subkey capable of signing try { - long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(signKeyId).getSecretSignId(); + long signSubKeyId = mKeyRepository.getSecretSignId(signKeyId); pgpData.setSignatureSubKeyId(signSubKeyId); - } catch (PgpKeyNotFoundException e) { + } catch (NotFoundException e) { throw new Exception("signing subkey not found!", e); } } @@ -230,7 +229,7 @@ public class OpenPgpService extends Service { if (signKeyId == Constants.key.none) { throw new Exception("No signing key given"); } - long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(signKeyId).getSecretSignId(); + long signSubKeyId = mKeyRepository.getSecretSignId(signKeyId); pgpData.setSignatureMasterKeyId(signKeyId) .setSignatureSubKeyId(signSubKeyId) @@ -627,7 +626,8 @@ public class OpenPgpService extends Service { } try { - CanonicalizedPublicKeyRing keyRing = mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId); + CanonicalizedPublicKeyRing keyRing = + mKeyRepository.getCanonicalizedPublicKeyRing(masterKeyId); Intent result = new Intent(); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); @@ -749,7 +749,7 @@ public class OpenPgpService extends Service { result.putExtra(OpenPgpApi.RESULT_PRIMARY_USER_ID, userId); result.putExtra(OpenPgpApi.RESULT_KEY_CREATION_TIME, creationTime); - } catch (PgpKeyNotFoundException e) { + } catch (NotFoundException e) { Timber.e(e, "Error loading key info"); return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage()); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java index 4851d0815..536bab373 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java @@ -48,6 +48,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ssh.AuthenticationData; @@ -59,8 +60,6 @@ import timber.log.Timber; public class SshAuthenticationService extends Service { - private static final String TAG = "SshAuthService"; - private ApiPermissionHelper mApiPermissionHelper; private KeyRepository mKeyRepository; private ApiAppDao mApiAppDao; @@ -144,23 +143,18 @@ public class SshAuthenticationService extends Service { AuthenticationData.Builder authData = AuthenticationData.builder(); authData.setAuthenticationMasterKeyId(masterKeyId); - CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId); - long authSubKeyId; int authSubKeyAlgorithm; String authSubKeyCurveOid = null; try { // get first usable subkey capable of authentication - authSubKeyId = cachedPublicKeyRing.getSecretAuthenticationId(); + authSubKeyId = mKeyRepository.getSecretAuthenticationId(masterKeyId); // needed for encoding the resulting signature authSubKeyAlgorithm = getPublicKey(masterKeyId).getAlgorithm(); if (authSubKeyAlgorithm == PublicKeyAlgorithmTags.ECDSA) { authSubKeyCurveOid = getPublicKey(masterKeyId).getCurveOid(); } - } catch (PgpKeyNotFoundException e) { - return createExceptionErrorResult(SshAuthenticationApiError.NO_AUTH_KEY, - "authentication key for master key id not found in keychain", e); - } catch (KeyRepository.NotFoundException e) { + } catch (NotFoundException e) { return createExceptionErrorResult(SshAuthenticationApiError.NO_SUCH_KEY, "Key for master key id not found", e); } @@ -272,7 +266,7 @@ public class SshAuthenticationService extends Service { try { description = getDescription(masterKeyId); - } catch (PgpKeyNotFoundException e) { + } catch (NotFoundException e) { return createExceptionErrorResult(SshAuthenticationApiError.NO_SUCH_KEY, "Could not create description", e); } @@ -372,8 +366,7 @@ public class SshAuthenticationService extends Service { return new SshPublicKeyResponse(sshPublicKeyBlob).toIntent(); } - private CanonicalizedPublicKey getPublicKey(long masterKeyId) - throws PgpKeyNotFoundException, KeyRepository.NotFoundException { + private CanonicalizedPublicKey getPublicKey(long masterKeyId) throws NotFoundException { KeyRepository keyRepository = KeyRepository.create(getApplicationContext()); long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId) .getAuthenticationId(); @@ -381,11 +374,11 @@ public class SshAuthenticationService extends Service { .getPublicKey(authSubKeyId); } - private String getDescription(long masterKeyId) throws PgpKeyNotFoundException { + private String getDescription(long masterKeyId) throws NotFoundException { CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId); String description = ""; - long authSubKeyId = cachedPublicKeyRing.getSecretAuthenticationId(); + long authSubKeyId = mKeyRepository.getSecretAuthenticationId(masterKeyId); description += cachedPublicKeyRing.getPrimaryUserId(); description += " (" + Long.toHexString(authSubKeyId) + ")"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java index 0644f6e95..4885ad8e5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java @@ -123,7 +123,7 @@ class RequestKeyPermissionPresenter { } CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId); - SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(candidateSubKeyId); + SecretKeyType secretKeyType = keyRepository.getSecretKeyType(candidateSubKeyId); if (secretKeyType.isUsable()) { return cachedPublicKeyRing; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index fe9fc4987..d88e7b74d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -18,6 +18,8 @@ package org.sufficientlysecure.keychain.service; +import java.util.Date; + import android.app.AlarmManager; import android.app.Notification; import android.app.PendingIntent; @@ -41,14 +43,11 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.NotificationIds; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; -import java.util.Date; - /** * This service runs in its own process, but is available to all other processes as the main * passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for @@ -246,8 +245,8 @@ public class PassphraseCacheService extends Service { + masterKeyId + ", subKeyId " + subKeyId); // get the type of key (from the database) - CachedPublicKeyRing keyRing = KeyRepository.create(this).getCachedPublicKeyRing(masterKeyId); - SecretKeyType keyType = keyRing.getSecretKeyType(subKeyId); + KeyRepository keyRepository = KeyRepository.create(this); + SecretKeyType keyType = keyRepository.getSecretKeyType(subKeyId); switch (keyType) { case PASSPHRASE_EMPTY: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java index 3df7f8724..31db064cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java @@ -17,8 +17,12 @@ package org.sufficientlysecure.keychain.ssh; + +import java.util.Collection; + import android.content.Context; import android.support.annotation.NonNull; + import org.bouncycastle.openpgp.AuthenticationSignatureGenerator; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; @@ -29,15 +33,13 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; import timber.log.Timber; -import java.util.Collection; - import static java.lang.String.format; @@ -101,8 +103,8 @@ public class AuthenticationOperation extends BaseOperation Long authSubKeyId = data.getAuthenticationSubKeyId(); if (authSubKeyId == null) { try { // Get the key id of the authentication key belonging to the master key id - authSubKeyId = mKeyRepository.getCachedPublicKeyRing(authMasterKeyId).getSecretAuthenticationId(); - } catch (PgpKeyNotFoundException e) { + authSubKeyId = mKeyRepository.getSecretAuthenticationId(authMasterKeyId); + } catch (NotFoundException e) { log.add(LogType.MSG_AUTH_ERROR_KEY_AUTH, indent); return new AuthenticationResult(AuthenticationResult.RESULT_ERROR, log); } @@ -142,9 +144,7 @@ public class AuthenticationOperation extends BaseOperation CanonicalizedSecretKey.SecretKeyType secretKeyType; try { - secretKeyType = mKeyRepository - .getCachedPublicKeyRing(authMasterKeyId) - .getSecretKeyType(authSubKeyId); + secretKeyType = mKeyRepository.getSecretKeyType(authSubKeyId); } catch (KeyRepository.NotFoundException e) { log.add(LogType.MSG_AUTH_ERROR_KEY_AUTH, indent); return new AuthenticationResult(AuthenticationResult.RESULT_ERROR, log); 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 38577923a..5f31f882e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -36,9 +36,9 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -75,7 +75,7 @@ public class CertifyKeyFragment if (key.canCertify()) { mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } - } catch (PgpKeyNotFoundException e) { + } catch (NotFoundException e) { Timber.e(e, "certify certify check failed"); } } 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 f1446a26d..a45c5777f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -48,9 +48,9 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; @@ -413,10 +413,10 @@ public class CreateKeyFinalFragment extends Fragment { KeyRepository keyRepository = KeyRepository.create(getContext()); SaveKeyringParcel.Builder builder; - CachedPublicKeyRing key = keyRepository.getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); try { + CachedPublicKeyRing key = keyRepository.getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); builder = SaveKeyringParcel.buildChangeKeyringParcel(saveKeyResult.mMasterKeyId, key.getFingerprint()); - } catch (PgpKeyNotFoundException e) { + } catch (NotFoundException e) { Timber.e("Key that should be moved to Security Token not found in database!"); return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java index a495deb9a..18d99741b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java @@ -38,6 +38,7 @@ import android.widget.Spinner; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.RevokeResult; @@ -86,35 +87,23 @@ public class DeleteKeyDialogActivity extends FragmentActivity { if (mMasterKeyIds.length == 1 && mHasSecret) { // if mMasterKeyIds.length == 0 we let the DeleteOperation respond - try { - KeyRepository keyRepository = KeyRepository.create(this); - HashMap data = keyRepository.getUnifiedData( - mMasterKeyIds[0], new String[]{ - KeychainContract.KeyRings.NAME, - KeychainContract.KeyRings.IS_REVOKED - }, new int[]{ - KeyRepository.FIELD_TYPE_STRING, - KeyRepository.FIELD_TYPE_INTEGER - } - ); - - String name; - - name = (String) data.get(KeychainContract.KeyRings.NAME); - - if (name == null) { - name = getString(R.string.user_id_no_name); - } - - if ((long) data.get(KeychainContract.KeyRings.IS_REVOKED) > 0) { - showNormalDeleteDialog(); - } else { - showRevokeDeleteDialog(name); - } - } catch (KeyRepository.NotFoundException e) { - Timber.e(e, "Secret key to delete not found at DeleteKeyDialogActivity for " - + mMasterKeyIds[0]); + KeyRepository keyRepository = KeyRepository.create(this); + UnifiedKeyInfo keyInfo = keyRepository.getUnifiedKeyInfo(mMasterKeyIds[0]); + if (keyInfo == null) { + Timber.e("Secret key to delete not found at DeleteKeyDialogActivity for " + mMasterKeyIds[0]); finish(); + return; + } + + String name = keyInfo.name(); + if (name == null) { + name = getString(R.string.user_id_no_name); + } + + if (keyInfo.is_revoked()) { + showNormalDeleteDialog(); + } else { + showRevokeDeleteDialog(name); } } else { showNormalDeleteDialog(); @@ -269,36 +258,26 @@ public class DeleteKeyDialogActivity extends FragmentActivity { if (masterKeyIds.length == 1) { long masterKeyId = masterKeyIds[0]; - try { - HashMap data = KeyRepository.create(getContext()) - .getUnifiedData( - masterKeyId, new String[]{ - KeychainContract.KeyRings.NAME, - KeychainContract.KeyRings.HAS_ANY_SECRET - }, new int[]{ - KeyRepository.FIELD_TYPE_STRING, - KeyRepository.FIELD_TYPE_INTEGER - } - ); - String name; - - name = (String) data.get(KeychainContract.KeyRings.NAME); - if (name == null) { - name = getString(R.string.user_id_no_name); - } - - if (hasSecret) { - // show title only for secret key deletions, - // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior - builder.setTitle(getString(R.string.title_delete_secret_key, name)); - mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); - } else { - mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); - } - } catch (KeyRepository.NotFoundException e) { + KeyRepository keyRepository = KeyRepository.create(getContext()); + UnifiedKeyInfo keyInfo = keyRepository.getUnifiedKeyInfo(masterKeyId); + if (keyInfo == null) { dismiss(); return null; } + String name = keyInfo.name(); + + if (name == null) { + name = getString(R.string.user_id_no_name); + } + + if (keyInfo.has_any_secret()) { + // show title only for secret key deletions, + // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior + builder.setTitle(getString(R.string.title_delete_secret_key, name)); + mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); + } else { + mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); + } } else { mMainMessage.setText(R.string.key_deletion_confirmation_multi); } 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 dd851052e..56cd80afd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -32,7 +32,6 @@ import com.tokenautocomplete.TokenCompleteTextView.TokenListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; @@ -142,7 +141,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { if (keyring.hasAnySecret()) { mSignKeySpinner.setPreSelectedKeyId(signatureKeyId); } - } catch (PgpKeyNotFoundException e) { + } catch (NotFoundException e) { Timber.e(e, "key not found for signing!"); Notify.create(getActivity(), getString(R.string.error_preselect_sign_key, KeyFormattingUtils.beautifyKeyId(signatureKeyId)), diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 4e48100ed..306bf0657 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -61,7 +61,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -115,10 +114,9 @@ public class PassphraseDialogActivity extends FragmentActivity { // handle empty passphrases by directly returning an empty crypto input parcel try { - CachedPublicKeyRing pubRing = - KeyRepository.create(this).getCachedPublicKeyRing(requiredInput.getMasterKeyId()); + KeyRepository keyRepository = KeyRepository.create(this); // use empty passphrase for empty passphrase - if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) { + if (keyRepository.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) { // also return passphrase back to activity Intent returnIntent = new Intent(); cryptoInputParcel = cryptoInputParcel.withPassphrase(new Passphrase(""), requiredInput.getSubKeyId()); @@ -299,7 +297,7 @@ public class PassphraseDialogActivity extends FragmentActivity { userId = getString(R.string.user_id_no_name); } - keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId); + keyType = keyRepository.getSecretKeyType(subKeyId); switch (keyType) { case PASSPHRASE: message = getString(R.string.passphrase_for, userId); @@ -316,7 +314,7 @@ public class PassphraseDialogActivity extends FragmentActivity { throw new AssertionError("Unhandled SecretKeyType (should not happen)"); } } - } catch (PgpKeyNotFoundException | KeyRepository.NotFoundException e) { + } catch (KeyRepository.NotFoundException e) { alert.setTitle(R.string.title_key_not_found); alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId())); alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index 902003d41..5ef4d5358 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -54,8 +54,8 @@ import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.Certification.CertDetails; import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; @@ -382,9 +382,8 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB byte[] fingerprint; try { - fingerprint = KeyRepository.create(activity).getCachedPublicKeyRing( - masterKeyId).getFingerprint(); - } catch (PgpKeyNotFoundException e) { + fingerprint = KeyRepository.create(activity).getCachedPublicKeyRing(masterKeyId).getFingerprint(); + } catch (NotFoundException e) { throw new IllegalStateException("Key to verify linked id for must exist in db!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index c69f91e26..7772202ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -483,8 +483,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private boolean keyHasPassphrase() { try { long masterKeyId = unifiedKeyInfo.master_key_id(); - SecretKeyType secretKeyType = - keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretKeyType(masterKeyId); + SecretKeyType secretKeyType = keyRepository.getSecretKeyType(masterKeyId); switch (secretKeyType) { // all of these make no sense to ask case PASSPHRASE_EMPTY: diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java index a0be35c87..767d446cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java @@ -29,14 +29,12 @@ import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.v4.content.AsyncTaskLoader; import android.text.TextUtils; -import android.util.Log; import com.google.auto.value.AutoValue; import okhttp3.Call; import okhttp3.HttpUrl; import okhttp3.Request.Builder; import okhttp3.Response; -import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient; import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryFailedException; @@ -48,11 +46,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyRetrievalResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; @@ -120,7 +115,6 @@ public abstract class PublicKeyRetrievalLoader extends AsyncTaskLoader 1 AND ( expiry IS NULL OR expiry >= date('now') ) + AND can_sign = 1 AND master_key_id = ?; + +selectEffectiveAuthKeyIdByMasterKeyId: +SELECT key_id + FROM keys + WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= date('now') ) + AND can_authenticate = 1 AND master_key_id = ?; + -- TODO move to KeySignatures.sq selectMasterKeyIdsBySigner: SELECT master_key_id - FROM key_signatures WHERE signer_key_id IN ?; \ No newline at end of file + FROM key_signatures WHERE signer_key_id IN ?; \ No newline at end of file diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java index 31a1ebd3a..aad764af5 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/KeychainTestRunner.java @@ -1,5 +1,6 @@ package org.sufficientlysecure.keychain; + import org.junit.runners.model.InitializationError; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java index 8f3726895..17cea489d 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java @@ -19,6 +19,13 @@ package org.sufficientlysecure.keychain.operations; +import java.io.PrintStream; +import java.security.MessageDigest; +import java.security.PublicKey; +import java.security.Security; +import java.security.Signature; +import java.util.ArrayList; + import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.jcajce.provider.asymmetric.eddsa.EdDSAEngine; import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSANamedCurveTable; @@ -44,13 +51,6 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.util.Passphrase; -import java.io.PrintStream; -import java.security.MessageDigest; -import java.security.PublicKey; -import java.security.Security; -import java.security.Signature; -import java.util.ArrayList; - @RunWith(KeychainTestRunner.class) public class AuthenticationOperationTest { @@ -160,7 +160,7 @@ public class AuthenticationOperationTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); long masterKeyId = mStaticRingRsa.getMasterKeyId(); - Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + Long authSubKeyId = keyRepository.getSecretAuthenticationId(masterKeyId); { // sign challenge AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, @@ -206,7 +206,7 @@ public class AuthenticationOperationTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); long masterKeyId = mStaticRingEcDsa.getMasterKeyId(); - Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + Long authSubKeyId = keyRepository.getSecretAuthenticationId(masterKeyId); { // sign challenge AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, @@ -252,7 +252,7 @@ public class AuthenticationOperationTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); long masterKeyId = mStaticRingEdDsa.getMasterKeyId(); - Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + Long authSubKeyId = keyRepository.getSecretAuthenticationId(masterKeyId); { // sign challenge AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, @@ -300,7 +300,7 @@ public class AuthenticationOperationTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); long masterKeyId = mStaticRingDsa.getMasterKeyId(); - Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + Long authSubKeyId = keyRepository.getSecretAuthenticationId(masterKeyId); { // sign challenge AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, @@ -345,7 +345,7 @@ public class AuthenticationOperationTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); long masterKeyId = mStaticRingEcDsa.getMasterKeyId(); - Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + Long authSubKeyId = keyRepository.getSecretAuthenticationId(masterKeyId); { // sign challenge - should succeed with selected key allowed AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java index 7417d4314..1dd1b69bb 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java @@ -162,15 +162,6 @@ public class BackupOperationTest { assertTrue("export must be a success", result); - long masterKeyId1, masterKeyId2; - if (mStaticRing1.getMasterKeyId() < mStaticRing2.getMasterKeyId()) { - masterKeyId1 = mStaticRing1.getMasterKeyId(); - masterKeyId2 = mStaticRing2.getMasterKeyId(); - } else { - masterKeyId2 = mStaticRing1.getMasterKeyId(); - masterKeyId1 = mStaticRing2.getMasterKeyId(); - } - IteratorWithIOThrow unc = UncachedKeyRing.fromStream(new ByteArrayInputStream(out.toByteArray())); @@ -178,7 +169,7 @@ public class BackupOperationTest { assertTrue("export must have two keys (1/2)", unc.hasNext()); UncachedKeyRing ring = unc.next(); Assert.assertEquals("first exported key has correct masterkeyid", - masterKeyId1, ring.getMasterKeyId()); + mStaticRing2.getMasterKeyId(), ring.getMasterKeyId()); assertFalse("first exported key must not be secret", ring.isSecret()); assertFalse("there must be no local signatures in an exported keyring", checkForLocal(ring)); @@ -188,7 +179,7 @@ public class BackupOperationTest { assertTrue("export must have two keys (2/2)", unc.hasNext()); UncachedKeyRing ring = unc.next(); Assert.assertEquals("second exported key has correct masterkeyid", - masterKeyId2, ring.getMasterKeyId()); + mStaticRing1.getMasterKeyId(), ring.getMasterKeyId()); assertFalse("second exported key must not be secret", ring.isSecret()); assertFalse("there must be no local signatures in an exported keyring", checkForLocal(ring)); @@ -205,7 +196,7 @@ public class BackupOperationTest { assertTrue("export must have four keys (1/4)", unc.hasNext()); UncachedKeyRing ring = unc.next(); Assert.assertEquals("1/4 exported key has correct masterkeyid", - masterKeyId1, ring.getMasterKeyId()); + mStaticRing2.getMasterKeyId(), ring.getMasterKeyId()); assertFalse("1/4 exported key must not be public", ring.isSecret()); assertFalse("there must be no local signatures in an exported keyring", checkForLocal(ring)); @@ -213,7 +204,7 @@ public class BackupOperationTest { assertTrue("export must have four keys (2/4)", unc.hasNext()); ring = unc.next(); Assert.assertEquals("2/4 exported key has correct masterkeyid", - masterKeyId1, ring.getMasterKeyId()); + mStaticRing2.getMasterKeyId(), ring.getMasterKeyId()); assertTrue("2/4 exported key must be public", ring.isSecret()); assertFalse("there must be no local signatures in an exported keyring", checkForLocal(ring)); @@ -223,7 +214,7 @@ public class BackupOperationTest { assertTrue("export must have four keys (3/4)", unc.hasNext()); UncachedKeyRing ring = unc.next(); Assert.assertEquals("3/4 exported key has correct masterkeyid", - masterKeyId2, ring.getMasterKeyId()); + mStaticRing1.getMasterKeyId(), ring.getMasterKeyId()); assertFalse("3/4 exported key must not be public", ring.isSecret()); assertFalse("there must be no local signatures in an exported keyring", checkForLocal(ring)); @@ -231,7 +222,7 @@ public class BackupOperationTest { assertTrue("export must have four keys (4/4)", unc.hasNext()); ring = unc.next(); Assert.assertEquals("4/4 exported key has correct masterkeyid", - masterKeyId2, ring.getMasterKeyId()); + mStaticRing1.getMasterKeyId(), ring.getMasterKeyId()); assertTrue("4/4 exported key must be public", ring.isSecret()); assertFalse("there must be no local signatures in an exported keyring", checkForLocal(ring)); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java index e28c1fc10..89ca8c060 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java @@ -149,8 +149,8 @@ public class CertifyOperationTest { { CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); - Assert.assertEquals("public key must not be marked verified prior to certification", - VerificationStatus.UNVERIFIED, ring.getVerified()); + Assert.assertNull("public key must not be marked verified prior to certification", + ring.getVerified()); } CertifyActionsParcel.Builder actions = CertifyActionsParcel.builder(mStaticRing1.getMasterKeyId()); @@ -164,21 +164,20 @@ public class CertifyOperationTest { CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); Assert.assertEquals("new key must be verified now", - VerificationStatus.UNVERIFIED, ring.getVerified()); + VerificationStatus.VERIFIED_SECRET, ring.getVerified()); } } @Test public void testCertifyAttribute() throws Exception { - CertifyOperation op = new CertifyOperation(RuntimeEnvironment.application, - KeyWritableRepository.create(RuntimeEnvironment.application), null, null); + KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(RuntimeEnvironment.application); + CertifyOperation op = new CertifyOperation(RuntimeEnvironment.application, keyWritableRepository, null, null); { - CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) - .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); - Assert.assertEquals("public key must not be marked verified prior to certification", - VerificationStatus.UNVERIFIED, ring.getVerified()); + CanonicalizedPublicKeyRing ring = keyWritableRepository.getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); + Assert.assertNull("public key must not be marked verified prior to certification", + ring.getVerified()); } CertifyActionsParcel.Builder actions = CertifyActionsParcel.builder(mStaticRing1.getMasterKeyId()); @@ -189,8 +188,7 @@ public class CertifyOperationTest { Assert.assertTrue("certification must succeed", result.success()); { - CanonicalizedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) - .getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); + CanonicalizedPublicKeyRing ring = keyWritableRepository.getCanonicalizedPublicKeyRing(mStaticRing2.getMasterKeyId()); Assert.assertEquals("new key must be verified now", VerificationStatus.VERIFIED_SECRET, ring.getVerified()); } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java index 033908a44..db0fa2a5c 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java @@ -105,8 +105,9 @@ public class PromoteKeyOperationTest { @Test public void testPromote() throws Exception { + KeyWritableRepository keyRepository = KeyWritableRepository.create(RuntimeEnvironment.application); PromoteKeyOperation op = new PromoteKeyOperation(RuntimeEnvironment.application, - KeyWritableRepository.create(RuntimeEnvironment.application), null, null); + keyRepository, null, null); PromoteKeyResult result = op.execute( PromoteKeyringParcel.createPromoteKeyringParcel(mStaticRing.getMasterKeyId(), null, null), null); @@ -114,15 +115,14 @@ public class PromoteKeyOperationTest { Assert.assertTrue("promotion must succeed", result.success()); { - CachedPublicKeyRing ring = KeyWritableRepository.create(RuntimeEnvironment.application) - .getCachedPublicKeyRing(mStaticRing.getMasterKeyId()); + CachedPublicKeyRing ring = keyRepository.getCachedPublicKeyRing(mStaticRing.getMasterKeyId()); Assert.assertTrue("key must have a secret now", ring.hasAnySecret()); Iterator it = mStaticRing.getPublicKeys(); while (it.hasNext()) { long keyId = it.next().getKeyId(); Assert.assertEquals("all subkeys must be gnu dummy", - SecretKeyType.GNU_DUMMY, ring.getSecretKeyType(keyId)); + SecretKeyType.GNU_DUMMY, keyRepository.getSecretKeyType(keyId)); } } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 0a6f03932..39deab677 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -36,6 +36,7 @@ import org.bouncycastle.bcpg.PublicKeyEncSessionPacket; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -54,7 +55,7 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength; import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureEncryptionAlgorithm; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java index c96cc1ea2..37108971a 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java @@ -55,6 +55,7 @@ import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; @@ -672,7 +673,7 @@ public class PgpKeyOperationTest { resetBuilder(); builder.addRevokeSubkey(123L); - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), VerificationStatus.UNVERIFIED); UncachedKeyRing otherModified = op.modifySecretKeyRing(secretRing, cryptoInput, builder.build()).getRing(); Assert.assertNull("revoking a nonexistent subkey should fail", otherModified); @@ -869,7 +870,7 @@ public class PgpKeyOperationTest { securityTokenBuilder.addOrReplaceSubkeyChange(SubkeyChange.createMoveToSecurityTokenChange(keyId)); CanonicalizedSecretKeyRing secretRing = - new CanonicalizedSecretKeyRing(ringSecurityToken.getEncoded(), 0); + new CanonicalizedSecretKeyRing(ringSecurityToken.getEncoded(), VerificationStatus.UNVERIFIED); PgpKeyOperation op = new PgpKeyOperation(null); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, securityTokenBuilder.build()); Assert.assertTrue("moveKeyToSecurityToken operation should be pending", result.isPending()); @@ -904,7 +905,7 @@ public class PgpKeyOperationTest { securityTokenBuilder.addOrReplaceSubkeyChange(SubkeyChange.createRecertifyChange(keyId, true)); CanonicalizedSecretKeyRing secretRing = - new CanonicalizedSecretKeyRing(modified.getEncoded(), 0); + new CanonicalizedSecretKeyRing(modified.getEncoded(), VerificationStatus.UNVERIFIED); PgpKeyOperation op = new PgpKeyOperation(null); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, securityTokenBuilder.build()); Assert.assertTrue("moveKeyToSecurityToken operation should be pending", result.isPending()); @@ -1187,7 +1188,7 @@ public class PgpKeyOperationTest { // we should still be able to modify it (and change its passphrase) without errors PgpKeyOperation op = new PgpKeyOperation(null); - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), VerificationStatus.UNVERIFIED); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, otherCryptoInput, builder.build()); Assert.assertTrue("key modification must succeed", result.success()); Assert.assertFalse("log must not contain a warning", @@ -1201,7 +1202,7 @@ public class PgpKeyOperationTest { modified = KeyringTestingHelper.injectPacket(modified, sKeyWithPassphrase.buf, sKeyWithPassphrase.position); PgpKeyOperation op = new PgpKeyOperation(null); - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(modified.getEncoded(), VerificationStatus.UNVERIFIED); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, CryptoInputParcel.createCryptoInputParcel(otherPassphrase2), builder.build()); Assert.assertTrue("key modification must succeed", result.success()); @@ -1214,7 +1215,7 @@ public class PgpKeyOperationTest { @Test public void testRestricted() throws Exception { - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), VerificationStatus.UNVERIFIED); builder.addUserId("discord"); PgpKeyOperation op = new PgpKeyOperation(null); @@ -1250,7 +1251,7 @@ public class PgpKeyOperationTest { try { Assert.assertTrue("modified keyring must be secret", ring.isSecret()); - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), VerificationStatus.UNVERIFIED); PgpKeyOperation op = new PgpKeyOperation(null); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel); @@ -1323,7 +1324,7 @@ public class PgpKeyOperationTest { SaveKeyringParcel parcel, CryptoInputParcel cryptoInput, LogType expected) throws Exception { - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), VerificationStatus.UNVERIFIED); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel); Assert.assertFalse(reason, result.success()); @@ -1337,7 +1338,7 @@ public class PgpKeyOperationTest { LogType expected) throws Exception { - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ring.getEncoded(), VerificationStatus.UNVERIFIED); PgpEditKeyResult result = op.modifySecretKeyRing(secretRing, cryptoInput, parcel); Assert.assertFalse(reason, result.success()); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java index abc71cd65..22beb189c 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/UncachedKeyringMergeTest.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; @@ -188,7 +189,7 @@ public class UncachedKeyringMergeTest { UncachedKeyRing modifiedA, modifiedB; { CanonicalizedSecretKeyRing secretRing = - new CanonicalizedSecretKeyRing(ringA.getEncoded(), 0); + new CanonicalizedSecretKeyRing(ringA.getEncoded(), VerificationStatus.UNVERIFIED); resetBuilder(); builder.addUserId("flim"); @@ -230,7 +231,7 @@ public class UncachedKeyringMergeTest { UncachedKeyRing modifiedA, modifiedB; long subKeyIdA, subKeyIdB; { - CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ringA.getEncoded(), 0); + CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing(ringA.getEncoded(), VerificationStatus.UNVERIFIED); resetBuilder(); builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd( @@ -278,7 +279,7 @@ public class UncachedKeyringMergeTest { resetBuilder(); builder.addRevokeSubkey(KeyringTestingHelper.getSubkeyId(ringA, 1)); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( - ringA.getEncoded(), 0); + ringA.getEncoded(), VerificationStatus.UNVERIFIED); modified = op.modifySecretKeyRing(secretRing, CryptoInputParcel.createCryptoInputParcel(new Date(), new Passphrase()), builder.build()).getRing(); } @@ -301,10 +302,10 @@ public class UncachedKeyringMergeTest { final UncachedKeyRing modified; { CanonicalizedPublicKeyRing publicRing = new CanonicalizedPublicKeyRing( - pubRing.getEncoded(), 0); + pubRing.getEncoded(), VerificationStatus.UNVERIFIED); CanonicalizedSecretKey secretKey = new CanonicalizedSecretKeyRing( - ringB.getEncoded(), 0).getSecretKey(); + ringB.getEncoded(), VerificationStatus.UNVERIFIED).getSecretKey(); secretKey.unlock(new Passphrase()); PgpCertifyOperation op = new PgpCertifyOperation(); CertifyAction action = CertifyAction.createForUserIds( @@ -379,7 +380,7 @@ public class UncachedKeyringMergeTest { builder.addUserAttribute(uat); CanonicalizedSecretKeyRing secretRing = new CanonicalizedSecretKeyRing( - ringA.getEncoded(), 0); + ringA.getEncoded(), VerificationStatus.UNVERIFIED); modified = op.modifySecretKeyRing(secretRing, CryptoInputParcel.createCryptoInputParcel(new Date(), new Passphrase()), builder.build()).getRing(); } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index abb9eaf99..9724a5fc6 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -27,8 +27,6 @@ import java.net.URL; import java.security.Security; import java.util.ArrayList; -import android.net.Uri; - import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.json.JSONArray; import org.json.JSONObject; @@ -45,14 +43,9 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.InputData; @@ -62,7 +55,7 @@ import org.sufficientlysecure.keychain.util.Passphrase; public class InteropTest { @BeforeClass - public static void setUpOnce() throws Exception { + public static void setUpOnce() { Security.insertProviderAt(new BouncyCastleProvider(), 1); ShadowLog.stream = System.out; } @@ -104,11 +97,11 @@ public class InteropTest { } } - private static final String asString(File json) throws Exception { + private static String asString(File json) throws Exception { return new String(asBytes(json), "utf-8"); } - private static final byte[] asBytes(File f) throws Exception { + private static byte[] asBytes(File f) throws Exception { FileInputStream fin = null; try { fin = new FileInputStream(f); @@ -123,16 +116,14 @@ public class InteropTest { private void runDecryptTest(JSONObject config, File base) throws Exception { File root = base.getParentFile(); String baseName = getBaseName(base); - CanonicalizedPublicKeyRing verify; + UncachedKeyRing verify; if (config.has("verifyKey")) { - verify = (CanonicalizedPublicKeyRing) - readRingFromFile(new File(root, config.getString("verifyKey"))); + verify = readUncachedRingFromFile(new File(root, config.getString("verifyKey"))); } else { verify = null; } - CanonicalizedSecretKeyRing decrypt = (CanonicalizedSecretKeyRing) - readRingFromFile(new File(root, config.getString("decryptKey"))); + UncachedKeyRing decrypt = readUncachedRingFromFile(new File(root, config.getString("decryptKey"))); ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayInputStream in = @@ -154,9 +145,10 @@ public class InteropTest { if (verify != null) { // Certain keys are too short, so we check appropriately. int code = result.getSignatureResult().getResult(); - Assert.assertTrue(base + ": should have a signature", - (code == OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE) || - (code == OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED)); + Assert.assertTrue(base + ": should have a signature (code: " + code + ")", + code == OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE || + code == OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED + || code == OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED); } OpenPgpMetadata metadata = result.getDecryptionMetadata(); Assert.assertEquals(base + ": filesize must be correct", @@ -168,11 +160,10 @@ public class InteropTest { private void runImportTest(JSONObject config, File base) throws Exception { File root = base.getParentFile(); String baseName = getBaseName(base); - CanonicalizedKeyRing pkr = - readRingFromFile(new File(root, baseName + ".asc")); + CanonicalizedKeyRing pkr = readRingFromFile(new File(root, baseName + ".asc")); // Check we have the correct uids. - ArrayList expected = new ArrayList(); + ArrayList expected = new ArrayList<>(); JSONArray uids = config.getJSONArray("expected_uids"); for (int i = 0; i < uids.length(); i++) { expected.add(uids.getString(i)); @@ -188,7 +179,7 @@ public class InteropTest { expected.add(subkeys.getJSONObject(i).getString("expected_fingerprint")); } } - ArrayList actual = new ArrayList(); + ArrayList actual = new ArrayList<>(); for (CanonicalizedPublicKey pk: pkr.publicKeyIterator()) { if (pk.isValid()) { actual.add(KeyFormattingUtils.convertFingerprintToHex(pk.getFingerprint())); @@ -204,7 +195,7 @@ public class InteropTest { } } - UncachedKeyRing readUncachedRingFromFile(File path) throws Exception { + private UncachedKeyRing readUncachedRingFromFile(File path) throws Exception { BufferedInputStream bin = null; try { bin = new BufferedInputStream(new FileInputStream(path)); @@ -214,87 +205,40 @@ public class InteropTest { } } - CanonicalizedKeyRing readRingFromFile(File path) throws Exception { + private CanonicalizedKeyRing readRingFromFile(File path) throws Exception { UncachedKeyRing ukr = readUncachedRingFromFile(path); OperationLog log = new OperationLog(); return ukr.canonicalize(log, 0); } - private static final void close(Closeable v) { + private static void close(Closeable v) { if (v != null) { try { v.close(); - } catch (Throwable any) { + } catch (Throwable ignored) { } } } - private static final String getBaseName(File base) { + private static String getBaseName(File base) { String name = base.getName(); return name.substring(0, name.length() - ".json".length()); } private PgpDecryptVerifyOperation makeOperation(final String msg, final Passphrase passphrase, - final CanonicalizedSecretKeyRing decrypt, final CanonicalizedPublicKeyRing verify) - throws Exception { + UncachedKeyRing decrypt, UncachedKeyRing verify) { + KeyWritableRepository keyRepository = KeyWritableRepository.create(RuntimeEnvironment.application); - final long decryptId = decrypt.getEncryptId(); - final Uri decryptUri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(decryptId); - final Uri verifyUri = verify != null ? - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(verify.getMasterKeyId()) : null; - - KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application, - KeychainDatabase.getInstance(RuntimeEnvironment.application), - LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application), - LocalSecretKeyStorage.getInstance(RuntimeEnvironment.application), - DatabaseNotifyManager.create(RuntimeEnvironment.application), - AutocryptPeerDao.getInstance(RuntimeEnvironment.application)) { + Assert.assertTrue(keyRepository.saveSecretKeyRing(decrypt).success()); + if (verify != null) { + Assert.assertTrue(keyRepository.savePublicKeyRing(verify).success()); + } + return new PgpDecryptVerifyOperation(RuntimeEnvironment.application, keyRepository, null) { @Override - public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException { - Assert.assertEquals(msg + ": query should be for the decryption key", queryUri, decryptUri); - return new CachedPublicKeyRing(this, queryUri) { - @Override - public long getMasterKeyId() throws PgpKeyNotFoundException { - return decrypt.getMasterKeyId(); - } - - @Override - public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { - return decrypt.getSecretKey(keyId).getSecretKeyTypeSuperExpensive(); - } - }; - } - - public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri q) - throws NotFoundException { - Assert.assertEquals(msg + ": query should be for verification key", q, verifyUri); - return verify; - } - - public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri q) - throws NotFoundException { - Assert.assertEquals(msg + ": query should be for the decryption key", q, decryptUri); - return decrypt; - } - - @Override - public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId) - throws NotFoundException { - Assert.assertEquals(msg + ": query should be for the decryption key", - masterKeyId, decrypt.getMasterKeyId()); - return decrypt; - } - }; - - return new PgpDecryptVerifyOperation(RuntimeEnvironment.application, helper, null) { - @Override - public Passphrase getCachedPassphrase(long masterKeyId, long subKeyId) - throws NoSecretKeyException { + public Passphrase getCachedPassphrase(long masterKeyId, long subKeyId) { Assert.assertEquals(msg + ": passphrase should be for the secret key", masterKeyId, decrypt.getMasterKeyId()); - Assert.assertEquals(msg + ": passphrase should refer to the decryption subkey", - subKeyId, decryptId); return passphrase; } }; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java index c4edf93c1..9941a380b 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java @@ -18,6 +18,10 @@ package org.sufficientlysecure.keychain.provider; + +import java.util.Arrays; +import java.util.Iterator; + import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.util.encoders.Hex; import org.junit.Assert; @@ -36,9 +40,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.util.IterableIterator; -import java.util.Arrays; -import java.util.Iterator; - @RunWith(KeychainTestRunner.class) public class KeyRepositorySaveTest { @@ -153,9 +154,8 @@ public class KeyRepositorySaveTest { Assert.assertTrue("canCertify() should be true", key.canCertify()); Assert.assertTrue("canSign() should be true", key.canSign()); - // cached Assert.assertEquals("all subkeys from CachedPublicKeyRing should be divert-to-key", - SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId())); + SecretKeyType.DIVERT_TO_CARD, mDatabaseInteractor.getSecretKeyType(key.getKeyId())); } { // second subkey @@ -169,7 +169,7 @@ public class KeyRepositorySaveTest { // cached Assert.assertEquals("all subkeys from CachedPublicKeyRing should be divert-to-key", - SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId())); + SecretKeyType.DIVERT_TO_CARD, mDatabaseInteractor.getSecretKeyType(key.getKeyId())); } { // third subkey @@ -183,7 +183,7 @@ public class KeyRepositorySaveTest { // cached Assert.assertEquals("all subkeys from CachedPublicKeyRing should be divert-to-key", - SecretKeyType.DIVERT_TO_CARD, cachedRing.getSecretKeyType(key.getKeyId())); + SecretKeyType.DIVERT_TO_CARD, mDatabaseInteractor.getSecretKeyType(key.getKeyId())); } Assert.assertFalse("keyring should have 3 subkeys (4)", it.hasNext()); @@ -233,14 +233,14 @@ public class KeyRepositorySaveTest { Assert.assertTrue("master key should have sign flag", ring.getPublicKey().canSign()); Assert.assertTrue("master key should have encrypt flag", ring.getPublicKey().canEncrypt()); - signId = mDatabaseInteractor.getCachedPublicKeyRing(masterKeyId).getSecretSignId(); + signId = mDatabaseInteractor.getSecretSignId(masterKeyId); Assert.assertNotEquals("encrypt id should not be 0", 0, signId); - Assert.assertNotEquals("encrypt key should be different from master key", masterKeyId, signId); + Assert.assertNotEquals("signing key should be different from master key", masterKeyId, signId); } { - CachedPublicKeyRing ring = mDatabaseInteractor.getCachedPublicKeyRing(masterKeyId); - Assert.assertEquals("signing key should be same id cached as uncached", signId, ring.getSecretSignId()); + Assert.assertEquals("signing key should be same id cached as uncached", + signId, mDatabaseInteractor.getSecretSignId(masterKeyId)); } } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java index 088105ffc..5b247c4fc 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java @@ -76,7 +76,7 @@ public class SshPublicKeyTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); long masterKeyId = mStaticRingEcDsa.getMasterKeyId(); - long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + long authSubKeyId = keyRepository.getSecretAuthenticationId(masterKeyId); CanonicalizedPublicKey canonicalizedPublicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) .getPublicKey(authSubKeyId); From 1635c261b8ed32c701026c58bfdd93daeb6fe625 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 26 Jun 2018 11:43:23 +0200 Subject: [PATCH 049/124] ditch CachedPublicKeyRing, and some cleanup --- .../keychain/operations/CertifyOperation.java | 4 +- .../KeybaseVerificationOperation.java | 35 +++--- .../keychain/pgp/CanonicalizedKeyRing.java | 21 +--- .../pgp/CanonicalizedPublicKeyRing.java | 2 +- .../keychain/pgp/CanonicalizedSecretKey.java | 2 +- .../keychain/pgp/KeyRing.java | 16 --- .../pgp/OpenPgpSignatureResultBuilder.java | 6 +- .../keychain/provider/AbstractDao.java | 10 ++ .../provider/CachedPublicKeyRing.java | 105 ------------------ .../keychain/provider/KeyRepository.java | 56 ++-------- .../keychain/remote/OpenPgpService.java | 21 ++-- .../remote/SshAuthenticationService.java | 15 +-- .../ui/RequestKeyPermissionActivity.java | 4 +- .../ui/RequestKeyPermissionPresenter.java | 25 ++--- .../keychain/ui/CertifyKeyFragment.java | 17 +-- .../keychain/ui/CreateKeyFinalFragment.java | 5 +- .../ui/EncryptModeAsymmetricFragment.java | 18 ++- .../keychain/ui/PassphraseDialogActivity.java | 27 ++--- .../keychain/ui/ViewKeyAdvShareFragment.java | 8 +- .../ui/adapter/ImportKeysAdapter.java | 11 +- .../ui/keyview/LinkedIdViewFragment.java | 2 +- .../org/sufficientlysecure/keychain/Keys.sq | 5 + .../operations/PromoteKeyOperationTest.java | 6 +- .../provider/KeyRepositorySaveTest.java | 6 +- 24 files changed, 119 insertions(+), 308 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java 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 ced8173cf..f7be25ec5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.operations; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; @@ -39,10 +38,9 @@ import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +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.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index efddc8269..c218248d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -18,16 +18,29 @@ package org.sufficientlysecure.keychain.operations; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.List; + import android.content.Context; import android.support.annotation.NonNull; import com.textuality.keybase.lib.KeybaseQuery; import com.textuality.keybase.lib.Proof; import com.textuality.keybase.lib.prover.Prover; - -import org.json.JSONObject; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Question; +import de.measite.minidns.Record; +import de.measite.minidns.record.Data; +import de.measite.minidns.record.TXT; import org.bouncycastle.openpgp.PGPUtil; +import org.json.JSONObject; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; +import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; @@ -35,28 +48,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.network.orbot.OrbotHelper; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.List; - -import de.measite.minidns.Client; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.record.Data; -import de.measite.minidns.record.TXT; public class KeybaseVerificationOperation extends BaseOperation { @@ -162,7 +159,7 @@ public class KeybaseVerificationOperation extends BaseOperation getEncryptIds() { HashSet result = new HashSet<>(); for (CanonicalizedPublicKey key : publicKeyIterator()) { @@ -130,15 +122,6 @@ public abstract class CanonicalizedKeyRing extends KeyRing { throw new PgpKeyNotFoundException("No valid encryption key found!"); } - public boolean hasEncrypt() throws PgpKeyNotFoundException { - try { - getEncryptId(); - return true; - } catch (PgpKeyNotFoundException e) { - return false; - } - } - public long getSigningId() throws PgpKeyNotFoundException { for(CanonicalizedPublicKey key : publicKeyIterator()) { if (key.canSign() && key.isValid()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java index 26ef31e7f..a9b24b4ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java @@ -100,7 +100,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing { * - the user id that matches the userIdToKeep parameter, or the primary user id if none matches * each with their most recent binding certificates */ - public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException, PgpKeyNotFoundException { + public CanonicalizedPublicKeyRing minimize(@Nullable String userIdToKeep) throws IOException { CanonicalizedPublicKey masterKey = getPublicKey(); PGPPublicKey masterPubKey = masterKey.getPublicKey(); boolean userIdStrippedOk = false; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index b37b572f4..ac2bcee4b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -326,7 +326,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { spGen.setSignatureCreationTime(false, creationTimestamp); signatureGenerator.setHashedSubpackets(spGen.generate()); return signatureGenerator; - } catch (PgpKeyNotFoundException | PGPException e) { + } catch (PGPException e) { // TODO: simply throw PGPException! throw new PgpGeneralException("Error initializing signature!", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java index e4d605ded..a2493dcab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java @@ -17,17 +17,12 @@ package org.sufficientlysecure.keychain.pgp; -import android.text.TextUtils; import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import java.io.Serializable; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * An abstract KeyRing. *

@@ -37,28 +32,17 @@ import java.util.regex.Pattern; * here. * * @see CanonicalizedKeyRing - * @see org.sufficientlysecure.keychain.provider.CachedPublicKeyRing */ public abstract class KeyRing { abstract public long getMasterKeyId() throws PgpKeyNotFoundException; - abstract public String getPrimaryUserId() throws PgpKeyNotFoundException; - abstract public String getPrimaryUserIdWithFallback() throws PgpKeyNotFoundException; - public UserId getSplitPrimaryUserIdWithFallback() throws PgpKeyNotFoundException { - return splitUserId(getPrimaryUserIdWithFallback()); - } - abstract public boolean isRevoked() throws PgpKeyNotFoundException; - abstract public boolean canCertify() throws PgpKeyNotFoundException; - abstract public long getEncryptId() throws PgpKeyNotFoundException; - abstract public boolean hasEncrypt() throws PgpKeyNotFoundException; - abstract public VerificationStatus getVerified() throws PgpKeyNotFoundException; /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 399b0152c..ea29c2e9c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -119,11 +119,7 @@ public class OpenPgpSignatureResultBuilder { // from RING setKeyId(signingRing.getMasterKeyId()); - try { - setPrimaryUserId(signingRing.getPrimaryUserIdWithFallback()); - } catch (PgpKeyNotFoundException e) { - Timber.d("No primary user id in keyring with master key id " + signingRing.getMasterKeyId()); - } + setPrimaryUserId(signingRing.getPrimaryUserIdWithFallback()); setSignatureKeyCertified(signingRing.getVerified() == VerificationStatus.VERIFIED_SECRET); List allUserIds = signingRing.getUnorderedUserIds(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java index 0a604bd2f..5eb4bc014 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java @@ -8,6 +8,8 @@ import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.db.SupportSQLiteQuery; import android.database.Cursor; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; + class AbstractDao { private final KeychainDatabase db; @@ -41,6 +43,14 @@ class AbstractDao { return result; } + T mapSingleRowOrThrow(SupportSQLiteQuery query, Mapper mapper) throws NotFoundException { + T result = mapSingleRow(query, mapper); + if (result == null) { + throw new NotFoundException(); + } + return result; + } + T mapSingleRow(SupportSQLiteQuery query, Mapper mapper) { try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToNext()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java deleted file mode 100644 index 425dc587d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.provider; - - -import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; - - -/** This implementation of KeyRing provides a cached view of PublicKeyRing - * objects based on database queries exclusively. - * - * This class should be used where only few points of data but no actual - * cryptographic operations are required about a PublicKeyRing which is already - * in the database. This happens commonly in UI code, where parsing of a PGP - * key for examination would be a very expensive operation. - * - * Each getter method is implemented using a more or less expensive database - * query, while object construction is (almost) free. A common pattern is - * mProviderHelper.getCachedKeyRing(uri).getterMethod() - * - * TODO Ensure that the values returned here always match the ones returned by - * the parsed KeyRing! - * - */ -public class CachedPublicKeyRing extends KeyRing { - private UnifiedKeyInfo unifiedKeyInfo; - - public CachedPublicKeyRing(UnifiedKeyInfo unifiedKeyInfo) { - this.unifiedKeyInfo = unifiedKeyInfo; - } - - @Override - public long getMasterKeyId() { - return unifiedKeyInfo.master_key_id(); - } - - public byte[] getFingerprint() { - return unifiedKeyInfo.fingerprint(); - } - - public long getCreationTime() { - return unifiedKeyInfo.creation(); - } - - @Override - public String getPrimaryUserId() { - return unifiedKeyInfo.user_id(); - } - - public String getPrimaryUserIdWithFallback() { - return getPrimaryUserId(); - } - - @Override - public boolean isRevoked() { - return unifiedKeyInfo.is_revoked(); - } - - @Override - public boolean canCertify() { - return unifiedKeyInfo.can_certify(); - } - - @Override - public long getEncryptId() { - return unifiedKeyInfo.has_encrypt_key_int(); - } - - @Override - public boolean hasEncrypt() { - return unifiedKeyInfo.has_encrypt_key(); - } - - public long getAuthenticationId() { - return unifiedKeyInfo.has_auth_key_int(); - } - - @Override - public VerificationStatus getVerified() { - return unifiedKeyInfo.verified(); - } - - public boolean hasAnySecret() { - return unifiedKeyInfo.has_any_secret(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 4c60c0c67..297741f9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import android.content.ContentResolver; @@ -102,15 +101,6 @@ public class KeyRepository extends AbstractDao { mLog = new OperationLog(); } - // replace with getUnifiedKeyInfo - public CachedPublicKeyRing getCachedPublicKeyRing(long masterKeyId) throws NotFoundException { - UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId); - if (unifiedKeyInfo == null) { - throw new NotFoundException(); - } - return new CachedPublicKeyRing(unifiedKeyInfo); - } - public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long masterKeyId) throws NotFoundException { UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId); if (unifiedKeyInfo == null) { @@ -146,22 +136,12 @@ public class KeyRepository extends AbstractDao { public Long getMasterKeyIdBySubkeyId(long subKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId); - try (Cursor cursor = getReadableDb().query(query)) { - if (cursor.moveToFirst()) { - return SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper().map(cursor); - } - return null; - } + return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()::map); } public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) { SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId); - try (Cursor cursor = getReadableDb().query(query)) { - if (cursor.moveToNext()) { - return SubKey.UNIFIED_KEY_INFO_MAPPER.map(cursor); - } - return null; - } + return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map); } public List getUnifiedKeyInfo(long... masterKeyIds) { @@ -190,13 +170,9 @@ public class KeyRepository extends AbstractDao { } public List getConfirmedUserIds(long masterKeyId) { - ArrayList userIds = new ArrayList<>(); SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification( Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET); - for (UserId userId : mapAllRows(query, UserPacket.USER_ID_MAPPER::map)) { - userIds.add(userId.user_id()); - } - return userIds; + return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id()); } public List getSubKeysByMasterKeyId(long masterKeyId) { @@ -206,12 +182,12 @@ public class KeyRepository extends AbstractDao { public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId); - try (Cursor cursor = getReadableDb().query(query)) { - if (cursor.moveToFirst()) { - return SubKey.SKT_MAPPER.map(cursor); - } - throw new NotFoundException(); - } + return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER::map); + } + + public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException { + SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId); + return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()::map); } private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException { @@ -267,20 +243,12 @@ public class KeyRepository extends AbstractDao { public long getSecretSignId(long masterKeyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId); - Long signKeyId = mapSingleRow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map); - if (signKeyId == null) { - throw new NotFoundException(); - } - return signKeyId; + return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map); } - public Long getSecretAuthenticationId(long masterKeyId) throws NotFoundException { + public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException { SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId); - Long authKeyId = mapSingleRow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map); - if (authKeyId == null) { - throw new NotFoundException(); - } - return authKeyId; + return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map); } public static class NotFoundException extends Exception { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index c6ee125de..8810b418a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -52,6 +52,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult.AutocryptPeerResult; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.BackupOperation; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ExportResult; @@ -68,7 +69,6 @@ import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; @@ -742,17 +742,16 @@ public class OpenPgpService extends Service { result.putExtra(OpenPgpApi.RESULT_SIGN_KEY_ID, signKeyId); if (signKeyId != Constants.key.none) { - try { - CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(signKeyId); - String userId = cachedPublicKeyRing.getPrimaryUserId(); - long creationTime = cachedPublicKeyRing.getCreationTime() * 1000; - - result.putExtra(OpenPgpApi.RESULT_PRIMARY_USER_ID, userId); - result.putExtra(OpenPgpApi.RESULT_KEY_CREATION_TIME, creationTime); - } catch (NotFoundException e) { - Timber.e(e, "Error loading key info"); - return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, e.getMessage()); + UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(signKeyId); + if (unifiedKeyInfo == null) { + Timber.e("Error loading key info"); + return createErrorResultIntent(OpenPgpError.GENERIC_ERROR, "Signing key not found!"); } + String userId = unifiedKeyInfo.user_id(); + long creationTime = unifiedKeyInfo.creation() * 1000; + + result.putExtra(OpenPgpApi.RESULT_PRIMARY_USER_ID, userId); + result.putExtra(OpenPgpApi.RESULT_KEY_CREATION_TIME, creationTime); } return result; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java index 536bab373..d9da21e44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java @@ -40,13 +40,13 @@ import org.openintents.ssh.authentication.response.PublicKeyResponse; import org.openintents.ssh.authentication.response.SigningResponse; import org.openintents.ssh.authentication.response.SshPublicKeyResponse; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -368,18 +368,19 @@ public class SshAuthenticationService extends Service { private CanonicalizedPublicKey getPublicKey(long masterKeyId) throws NotFoundException { KeyRepository keyRepository = KeyRepository.create(getApplicationContext()); - long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId) - .getAuthenticationId(); - return keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) - .getPublicKey(authSubKeyId); + UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(masterKeyId); + if (unifiedKeyInfo == null) { + throw new NotFoundException(); + } + return keyRepository.getCanonicalizedPublicKeyRing(masterKeyId).getPublicKey(unifiedKeyInfo.has_auth_key_int()); } private String getDescription(long masterKeyId) throws NotFoundException { - CachedPublicKeyRing cachedPublicKeyRing = mKeyRepository.getCachedPublicKeyRing(masterKeyId); + UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(masterKeyId); String description = ""; long authSubKeyId = mKeyRepository.getSecretAuthenticationId(masterKeyId); - description += cachedPublicKeyRing.getPrimaryUserId(); + description += unifiedKeyInfo.user_id(); description += " (" + Long.toHexString(authSubKeyId) + ")"; return description; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java index 27accfb26..884674445 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionActivity.java @@ -152,8 +152,8 @@ public class RequestKeyPermissionActivity extends FragmentActivity { } @Override - public void displayKeyInfo(UserId userId) { - keyUserIdView.setText(userId.name); + public void displayKeyInfo(String userIdName) { + keyUserIdView.setText(userIdName); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java index 4885ad8e5..4787392fe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java @@ -25,12 +25,11 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; -import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.remote.ApiPermissionHelper; @@ -94,18 +93,16 @@ class RequestKeyPermissionPresenter { } private void setRequestedMasterKeyId(long[] subKeyIds) throws PgpKeyNotFoundException { - CachedPublicKeyRing secretKeyRingOrPublicFallback = findSecretKeyRingOrPublicFallback(subKeyIds); + UnifiedKeyInfo secretKeyRingOrPublicFallback = findSecretKeyRingOrPublicFallback(subKeyIds); if (secretKeyRingOrPublicFallback == null) { throw new PgpKeyNotFoundException("No key found among requested!"); } - this.masterKeyId = secretKeyRingOrPublicFallback.getMasterKeyId(); + masterKeyId = secretKeyRingOrPublicFallback.master_key_id(); + view.displayKeyInfo(secretKeyRingOrPublicFallback.name()); - UserId userId = secretKeyRingOrPublicFallback.getSplitPrimaryUserIdWithFallback(); - view.displayKeyInfo(userId); - - if (secretKeyRingOrPublicFallback.hasAnySecret()) { + if (secretKeyRingOrPublicFallback.has_any_secret()) { view.switchToLayoutRequestKeyChoice(); } else { view.switchToLayoutNoSecret(); @@ -113,22 +110,22 @@ class RequestKeyPermissionPresenter { } @Nullable - private CachedPublicKeyRing findSecretKeyRingOrPublicFallback(long[] subKeyIds) { - CachedPublicKeyRing publicFallbackRing = null; + private UnifiedKeyInfo findSecretKeyRingOrPublicFallback(long[] subKeyIds) { + UnifiedKeyInfo publicFallbackRing = null; for (long candidateSubKeyId : subKeyIds) { try { Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(candidateSubKeyId); if (masterKeyId == null) { continue; } - CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId); + UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(masterKeyId); SecretKeyType secretKeyType = keyRepository.getSecretKeyType(candidateSubKeyId); if (secretKeyType.isUsable()) { - return cachedPublicKeyRing; + return unifiedKeyInfo; } if (publicFallbackRing == null) { - publicFallbackRing = cachedPublicKeyRing; + publicFallbackRing = unifiedKeyInfo; } } catch (NotFoundException e) { // no matter @@ -180,7 +177,7 @@ class RequestKeyPermissionPresenter { void setTitleText(String text); void setTitleClientIcon(Drawable drawable); - void displayKeyInfo(UserId userId); + void displayKeyInfo(String userIdName); void finish(); void finishAsCancelled(); 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 5f31f882e..24228b655 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -34,11 +34,10 @@ import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -47,7 +46,6 @@ import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.util.Preferences; -import timber.log.Timber; public class CertifyKeyFragment @@ -68,15 +66,10 @@ public class CertifyKeyFragment long certifyKeyId = getActivity().getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); if (certifyKeyId != Constants.key.none) { - try { - CachedPublicKeyRing key = (KeyRepository - .create(getContext())) - .getCachedPublicKeyRing(certifyKeyId); - if (key.canCertify()) { - mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); - } - } catch (NotFoundException e) { - Timber.e(e, "certify certify check failed"); + KeyRepository keyRepository = KeyRepository.create(getContext()); + UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(certifyKeyId); + if (unifiedKeyInfo != null && unifiedKeyInfo.can_certify()) { + mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } } } 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 a45c5777f..c13b85ede 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -48,7 +48,6 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; @@ -414,8 +413,8 @@ public class CreateKeyFinalFragment extends Fragment { SaveKeyringParcel.Builder builder; try { - CachedPublicKeyRing key = keyRepository.getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); - builder = SaveKeyringParcel.buildChangeKeyringParcel(saveKeyResult.mMasterKeyId, key.getFingerprint()); + byte[] fingerprint = keyRepository.getFingerprintByKeyId(saveKeyResult.mMasterKeyId); + builder = SaveKeyringParcel.buildChangeKeyringParcel(saveKeyResult.mMasterKeyId, fingerprint); } catch (NotFoundException e) { Timber.e("Key that should be moved to Security Token not found in database!"); return; 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 56cd80afd..0f92481ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -31,8 +31,8 @@ import android.widget.ViewAnimator; import com.tokenautocomplete.TokenCompleteTextView.TokenListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; @@ -136,16 +136,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { */ private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) { if (signatureKeyId != null) { - try { - CachedPublicKeyRing keyring = mKeyRepository.getCachedPublicKeyRing(signatureKeyId); - if (keyring.hasAnySecret()) { - mSignKeySpinner.setPreSelectedKeyId(signatureKeyId); - } - } catch (NotFoundException e) { - Timber.e(e, "key not found for signing!"); - Notify.create(getActivity(), getString(R.string.error_preselect_sign_key, - KeyFormattingUtils.beautifyKeyId(signatureKeyId)), - Style.ERROR).show(); + UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(signatureKeyId); + if (unifiedKeyInfo == null) { + String beautifyKeyId = KeyFormattingUtils.beautifyKeyId(signatureKeyId); + Notify.create(getActivity(), getString(R.string.error_preselect_sign_key, beautifyKeyId), Style.ERROR).show(); + } else if (unifiedKeyInfo.has_any_secret()) { + mSignKeySpinner.setPreSelectedKeyId(signatureKeyId); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 306bf0657..18ea51a54 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -52,13 +52,13 @@ import android.widget.ViewAnimator; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.PassphraseCacheService; @@ -285,11 +285,14 @@ public class PassphraseDialogActivity extends FragmentActivity { KeyRepository keyRepository = KeyRepository.create(getContext()); Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(subKeyId); - CachedPublicKeyRing cachedPublicKeyRing = keyRepository.getCachedPublicKeyRing(masterKeyId); + UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(masterKeyId); + if (unifiedKeyInfo == null) { + throw new NotFoundException(); + } // yes the inner try/catch block is necessary, otherwise the final variable // above can't be statically verified to have been set in all cases because // the catch clause doesn't return. - String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback(); + String mainUserId = unifiedKeyInfo.user_id(); OpenPgpUtils.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId); if (mainUserIdSplit.name != null) { userId = mainUserIdSplit.name; @@ -314,14 +317,10 @@ public class PassphraseDialogActivity extends FragmentActivity { throw new AssertionError("Unhandled SecretKeyType (should not happen)"); } } - } catch (KeyRepository.NotFoundException e) { + } catch (NotFoundException e) { alert.setTitle(R.string.title_key_not_found); alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId())); - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dismiss(); - } - }); + alert.setPositiveButton(android.R.string.ok, (dialog, which) -> dismiss()); alert.setCancelable(false); return alert.create(); } @@ -595,13 +594,9 @@ public class PassphraseDialogActivity extends FragmentActivity { } else { Timber.d("Caching entered passphrase"); - try { - PassphraseCacheService.addCachedPassphrase(getActivity(), - unlockedKey.getRing().getMasterKeyId(), unlockedKey.getKeyId(), passphrase, - unlockedKey.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds); - } catch (PgpKeyNotFoundException e) { - Timber.e(e, "adding of a passphrase failed"); - } + PassphraseCacheService.addCachedPassphrase(getActivity(), + unlockedKey.getRing().getMasterKeyId(), unlockedKey.getKeyId(), passphrase, + unlockedKey.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds); } finishCaching(passphrase, unlockedKey.getKeyId()); 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 89b6b2cfd..0d8a53bde 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -58,7 +58,6 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; @@ -140,14 +139,13 @@ public class ViewKeyAdvShareFragment extends Fragment { } private String getShareKeyContent(boolean asSshKey) - throws PgpKeyNotFoundException, KeyRepository.NotFoundException, IOException, PgpGeneralException, - NoSuchAlgorithmException { + throws KeyRepository.NotFoundException, IOException, PgpGeneralException, NoSuchAlgorithmException { KeyRepository keyRepository = KeyRepository.create(requireContext()); String content; if (asSshKey) { - long authSubKeyId = keyRepository.getCachedPublicKeyRing(unifiedKeyInfo.master_key_id()).getAuthenticationId(); + long authSubKeyId = unifiedKeyInfo.has_auth_key_int(); CanonicalizedPublicKey publicKey = keyRepository.getCanonicalizedPublicKeyRing(unifiedKeyInfo.master_key_id()) .getPublicKey(authSubKeyId); SshPublicKey sshPublicKey = new SshPublicKey(publicKey); @@ -224,7 +222,7 @@ public class ViewKeyAdvShareFragment extends Fragment { } catch (PgpGeneralException | IOException | NoSuchAlgorithmException e) { Timber.e(e, "error processing key!"); Notify.create(activity, R.string.error_key_processing, Notify.Style.ERROR).show(); - } catch (PgpKeyNotFoundException | KeyRepository.NotFoundException e) { + } catch (KeyRepository.NotFoundException e) { Timber.e(e, "key not found!"); Notify.create(activity, R.string.error_key_not_found, Notify.Style.ERROR).show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 162743758..94003d3eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -44,8 +44,6 @@ import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; @@ -88,16 +86,15 @@ public class ImportKeysAdapter extends RecyclerView.Adapter it = mStaticRing.getPublicKeys(); while (it.hasNext()) { diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java index 9941a380b..c3eb9dca8 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java @@ -31,6 +31,7 @@ import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; @@ -113,11 +114,11 @@ public class KeyRepositorySaveTest { mDatabaseInteractor.savePublicKeyRing(pub); - CachedPublicKeyRing cachedRing = mDatabaseInteractor.getCachedPublicKeyRing(keyId); + UnifiedKeyInfo unifiedKeyInfo = mDatabaseInteractor.getUnifiedKeyInfo(keyId); CanonicalizedPublicKeyRing pubRing = mDatabaseInteractor.getCanonicalizedPublicKeyRing(keyId); Assert.assertEquals("master key should be encryption key", keyId, pubRing.getEncryptId()); - Assert.assertEquals("master key should be encryption key (cached)", keyId, cachedRing.getEncryptId()); + Assert.assertEquals("master key should be encryption key (cached)", keyId, unifiedKeyInfo.has_encrypt_key_int()); Assert.assertEquals("canonicalized key flags should be zero", 0, (long) pubRing.getPublicKey().getKeyUsage()); @@ -139,7 +140,6 @@ public class KeyRepositorySaveTest { // make sure both the CanonicalizedSecretKeyRing as well as the CachedPublicKeyRing correctly // indicate the secret key type - CachedPublicKeyRing cachedRing = mDatabaseInteractor.getCachedPublicKeyRing(keyId); CanonicalizedSecretKeyRing secRing = mDatabaseInteractor.getCanonicalizedSecretKeyRing(keyId); Iterator it = secRing.secretKeyIterator().iterator(); From 83d5aafadb58efe7098795ada2f3b69a511d23a6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 26 Jun 2018 17:19:18 +0200 Subject: [PATCH 050/124] use LiveData for signing key spinners --- .../keychain/provider/KeyRepository.java | 2 + .../AppSettingsAllowedKeysListFragment.java | 2 +- .../ui/dialog/RemoteDeduplicateActivity.java | 98 +------- .../keychain/ui/CertifyKeyFragment.java | 17 +- .../ui/EncryptModeAsymmetricFragment.java | 31 +-- .../keychain/ui/KeyListFragment.java | 49 ++-- .../keychain/ui/adapter/KeyChoiceAdapter.java | 115 ++++++++++ .../keychain/ui/keyview/GenericViewModel.java | 23 ++ .../ui/keyview/LinkedIdViewFragment.java | 61 ++++- .../keychain/ui/keyview/ViewKeyActivity.java | 1 + .../keychain/ui/widget/CertifyKeySpinner.java | 136 ----------- .../ui/widget/KeyChoiceSpinnerAdapter.java | 211 ++++++++++++++++++ .../keychain/ui/widget/KeySpinner.java | 178 +++------------ .../keychain/ui/widget/SignKeySpinner.java | 92 -------- .../main/res/layout/certify_key_fragment.xml | 2 +- .../layout/encrypt_asymmetric_fragment.xml | 4 +- .../main/res/layout/keyspinner_item_none.xml | 2 +- .../res/layout/linked_id_view_fragment.xml | 4 +- 18 files changed, 492 insertions(+), 536 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 297741f9b..5eb4f1ad8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -25,6 +25,7 @@ import java.util.List; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; +import android.support.annotation.WorkerThread; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; @@ -43,6 +44,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import timber.log.Timber; +@WorkerThread public class KeyRepository extends AbstractDao { final ContentResolver contentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index ee9dbbf6c..b08d701ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -127,7 +127,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i Vector userIds = new Vector<>(); for (int i = 0; i < getListView().getCount(); ++i) { if (getListView().isItemChecked(i)) { - userIds.add(mAdapter.getUserId(i)); + userIds.add(spinnerAdapter.getUserId(i)); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java index 1b62788c8..1d932b7ef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java @@ -29,23 +29,16 @@ import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.res.Resources; import android.graphics.drawable.Drawable; -import android.graphics.drawable.Drawable.ConstantState; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.Adapter; -import android.text.format.DateUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -57,6 +50,7 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicatePresenter.RemoteDeduplicateView; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; @@ -263,94 +257,4 @@ public class RemoteDeduplicateActivity extends FragmentActivity { } } - private static class KeyChoiceAdapter extends Adapter { - private final LayoutInflater layoutInflater; - private final Resources resources; - private List data; - private Drawable iconUnselected; - private Drawable iconSelected; - private Integer activeItem; - - KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { - this.layoutInflater = layoutInflater; - this.resources = resources; - } - - @NonNull - @Override - public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false); - return new KeyChoiceViewHolder(keyChoiceItemView); - } - - @Override - public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { - UnifiedKeyInfo keyInfo = data.get(position); - Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; - holder.bind(keyInfo, icon); - } - - @Override - public int getItemCount() { - return data != null ? data.size() : 0; - } - - public void setData(List data) { - this.data = data; - notifyDataSetChanged(); - } - - void setSelectionDrawable(Drawable drawable) { - ConstantState constantState = drawable.getConstantState(); - if (constantState == null) { - return; - } - - iconSelected = constantState.newDrawable(resources); - - iconUnselected = constantState.newDrawable(resources); - DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); - - notifyDataSetChanged(); - } - - void setActiveItem(Integer newActiveItem) { - Integer prevActiveItem = this.activeItem; - this.activeItem = newActiveItem; - - if (prevActiveItem != null) { - notifyItemChanged(prevActiveItem); - } - if (newActiveItem != null) { - notifyItemChanged(newActiveItem); - } - } - } - - private static class KeyChoiceViewHolder extends RecyclerView.ViewHolder { - private final TextView vName; - private final TextView vCreation; - private final ImageView vIcon; - - KeyChoiceViewHolder(View itemView) { - super(itemView); - - vName = itemView.findViewById(R.id.key_list_item_name); - vCreation = itemView.findViewById(R.id.key_list_item_creation); - vIcon = itemView.findViewById(R.id.key_list_item_icon); - } - - void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { - vName.setText(keyInfo.name()); - - Context context = vCreation.getContext(); - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - vCreation.setText(context.getString(R.string.label_key_created, dateTime)); - - vIcon.setImageDrawable(selectionIcon); - } - } - } 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 24228b655..059cc683b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -20,8 +20,11 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import java.util.Date; +import java.util.List; import android.app.Activity; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.graphics.PorterDuff; import android.os.Bundle; @@ -42,9 +45,10 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; +import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Preferences; @@ -53,7 +57,7 @@ public class CertifyKeyFragment private CheckBox mUploadKeyCheckbox; - private CertifyKeySpinner mCertifyKeySpinner; + private KeySpinner mCertifyKeySpinner; private MultiUserIdsFragment mMultiUserIdsFragment; @@ -61,6 +65,13 @@ public class CertifyKeyFragment public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + LiveData> liveData = viewModel.getGenericLiveData(requireContext(), () -> { + KeyRepository keyRepository = KeyRepository.create(requireContext()); + return keyRepository.getAllUnifiedKeyInfoWithSecret(); + }); + liveData.observe(this, mCertifyKeySpinner::setData); + if (savedInstanceState == null) { // preselect certify key id if given long certifyKeyId = getActivity().getIntent() @@ -90,6 +101,8 @@ public class CertifyKeyFragment mMultiUserIdsFragment = (MultiUserIdsFragment) getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); + mCertifyKeySpinner.setShowNone(R.string.choice_select_cert); + // make certify image gray, like action icons ImageView vActionCertifyImage = view.findViewById(R.id.certify_key_action_certify_image); 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 0f92481ab..6e4c15675 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -22,7 +22,10 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; +import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -36,12 +39,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; -import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener; import org.sufficientlysecure.keychain.util.Passphrase; import timber.log.Timber; @@ -57,9 +60,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; - /** - * Creates new instance of this fragment - */ public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) { EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment(); @@ -75,23 +75,21 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { * Inflate the layout for this fragment */ @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); - mSignKeySpinner = view.findViewById(R.id.sign); + mSignKeySpinner = view.findViewById(R.id.sign_key_spinner); mEncryptKeyView = view.findViewById(R.id.recipient_list); mEncryptKeyView.setThreshold(1); // Start working from first character final ViewAnimator vSignatureIcon = view.findViewById(R.id.result_signature_icon); - mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() { - @Override - public void onKeyChanged(long masterKeyId) { - int child = masterKeyId != Constants.key.none ? 1 : 0; - if (vSignatureIcon.getDisplayedChild() != child) { - vSignatureIcon.setDisplayedChild(child); - } + mSignKeySpinner.setOnKeyChangedListener(masterKeyId -> { + int child = masterKeyId != Constants.key.none ? 1 : 0; + if (vSignatureIcon.getDisplayedChild() != child) { + vSignatureIcon.setDisplayedChild(child); } }); + mSignKeySpinner.setShowNone(R.string.cert_none); final ViewAnimator vEncryptionIcon = view.findViewById(R.id.result_encryption_icon); mEncryptKeyView.setTokenListener(new TokenListener() { @@ -117,7 +115,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mKeyRepository = KeyRepository.create(getContext()); + mKeyRepository = KeyRepository.create(requireContext()); + + GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + LiveData> liveData = viewModel.getGenericLiveData(requireContext(), + mKeyRepository::getAllUnifiedKeyInfoWithSecret); + liveData.observe(this, mSignKeySpinner::setData); // preselect keys given, from state or arguments if (savedInstanceState == null) { 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 635919a1c..24b59291f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -24,15 +24,12 @@ import java.util.List; import android.animation.ObjectAnimator; import android.app.Activity; import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProviders; -import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.support.annotation.NonNull; +import android.support.annotation.WorkerThread; 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; @@ -58,7 +55,6 @@ 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; @@ -78,8 +74,8 @@ import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectio import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItemFactory; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; -import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FabContainer; @@ -101,6 +97,9 @@ public class KeyListFragment extends RecyclerFragment> liveData = viewModel.getGenericLiveData(requireContext(), this::loadFlexibleKeyItems); + liveData.observe(this, this::onLoadKeyItems); } - public static class KeyListViewModel extends ViewModel { - LiveData> liveData; - - LiveData> getLiveData(Context context) { - if (liveData == null) { - liveData = new KeyListLiveData(context); - } - return liveData; - } - } - - public static class KeyListLiveData extends AsyncTaskLiveData> { - private final KeyRepository keyRepository; - private FlexibleKeyItemFactory flexibleKeyItemFactory; - - KeyListLiveData(@NonNull Context context) { - super(context, KeyRings.CONTENT_URI); - keyRepository = KeyRepository.create(context.getApplicationContext()); - flexibleKeyItemFactory = new FlexibleKeyItemFactory(context.getResources()); - } - - @Override - protected List asyncLoadData() { - List unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo(); - return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo); - } + @WorkerThread + private List loadFlexibleKeyItems() { + List unifiedKeyInfo = keyRepository.getAllUnifiedKeyInfo(); + return flexibleKeyItemFactory.mapUnifiedKeyInfoToFlexibleKeyItems(unifiedKeyInfo); } private void onLoadKeyItems(List flexibleKeyItems) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java new file mode 100644 index 000000000..1448e9f31 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -0,0 +1,115 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.List; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Drawable.ConstantState; +import android.support.annotation.NonNull; +import android.support.v4.content.res.ResourcesCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceViewHolder; + + +public class KeyChoiceAdapter extends Adapter { + private final LayoutInflater layoutInflater; + private final Resources resources; + private List data; + private Drawable iconUnselected; + private Drawable iconSelected; + private Integer activeItem; + + public KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { + this.layoutInflater = layoutInflater; + this.resources = resources; + } + + @NonNull + @Override + public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false); + return new KeyChoiceViewHolder(keyChoiceItemView); + } + + @Override + public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { + UnifiedKeyInfo keyInfo = data.get(position); + Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; + holder.bind(keyInfo, icon); + } + + @Override + public int getItemCount() { + return data != null ? data.size() : 0; + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + + public void setSelectionDrawable(Drawable drawable) { + ConstantState constantState = drawable.getConstantState(); + if (constantState == null) { + return; + } + + iconSelected = constantState.newDrawable(resources); + + iconUnselected = constantState.newDrawable(resources); + DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); + + notifyDataSetChanged(); + } + + public void setActiveItem(Integer newActiveItem) { + Integer prevActiveItem = this.activeItem; + this.activeItem = newActiveItem; + + if (prevActiveItem != null) { + notifyItemChanged(prevActiveItem); + } + if (newActiveItem != null) { + notifyItemChanged(newActiveItem); + } + } + + public static class KeyChoiceViewHolder extends RecyclerView.ViewHolder { + private final TextView vName; + private final TextView vCreation; + private final ImageView vIcon; + + KeyChoiceViewHolder(View itemView) { + super(itemView); + + vName = itemView.findViewById(R.id.key_list_item_name); + vCreation = itemView.findViewById(R.id.key_list_item_creation); + vIcon = itemView.findViewById(R.id.key_list_item_icon); + } + + void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { + vName.setText(keyInfo.name()); + + Context context = vCreation.getContext(); + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | + DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); + vCreation.setText(context.getString(R.string.label_key_created, dateTime)); + + vIcon.setImageDrawable(selectionIcon); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java new file mode 100644 index 000000000..ab4054297 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java @@ -0,0 +1,23 @@ +package org.sufficientlysecure.keychain.ui.keyview; + + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.content.Context; + +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.livedata.GenericLiveData.GenericDataLoader; + + +/** A simple generic ViewModel that can be used if exactly one field of data needs to be stored. */ +public class GenericViewModel extends ViewModel { + private LiveData genericLiveData; + + public LiveData getGenericLiveData(Context context, GenericDataLoader func) { + if (genericLiveData == null) { + genericLiveData = new GenericLiveData<>(context, null, func); + } + // noinspection unchecked + return genericLiveData; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index 0e59421d0..664946a9a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -19,8 +19,11 @@ package org.sufficientlysecure.keychain.ui.keyview; import java.util.Collections; +import java.util.List; import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; import android.graphics.PorterDuff; @@ -52,6 +55,7 @@ import org.sufficientlysecure.keychain.linked.UriAttribute; import org.sufficientlysecure.keychain.livedata.CertificationDao; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.Certification.CertDetails; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeyRepository; @@ -69,7 +73,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; import org.sufficientlysecure.keychain.ui.widget.CertListWidget; -import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; +import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import timber.log.Timber; @@ -118,11 +122,15 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB isSecret = args.getBoolean(ARG_IS_SECRET); masterKeyId = args.getLong(ARG_MASTER_KEY_ID); + } - IdentityDao identityDao = IdentityDao.getInstance(getContext()); - GenericLiveData uriAttributeLiveData = - new GenericLiveData<>(getContext(), null, () -> identityDao.getLinkedIdInfo(masterKeyId, lidRank)); - uriAttributeLiveData.observe(this, this::onLinkedIdInfoLoaded); + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class); + viewModel.getLinkedIdInfo(requireContext(), masterKeyId, lidRank).observe(this, this::onLinkedIdInfoLoaded); + viewModel.getCertifyingKeys(requireContext()).observe(this, viewHolder.vKeySpinner::setData); } private void onLinkedIdInfoLoaded(LinkedIdInfo linkedIdInfo) { @@ -191,7 +199,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB private ViewAnimator vButtonSwitcher; private CertListWidget vLinkedCerts; - private CertifyKeySpinner vKeySpinner; + private KeySpinner vKeySpinner; private final View vButtonVerify; private final View vButtonRetry; private final View vButtonConfirm; @@ -236,6 +244,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB if (!isSecret) { showButton(2); if (!vKeySpinner.isSingleEntry()) { + vKeySpinner.setShowNone(R.string.choice_select_cert); vKeySpinnerContainer.setVisibility(View.VISIBLE); } } else { @@ -351,10 +360,8 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource()); viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying()); - CertificationDao certificationDao = CertificationDao.getInstance(context); - LiveData certDetailsLiveData = new GenericLiveData<>( - context, null, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank)); - certDetailsLiveData.observe(this, this::onLoadCertDetails); + LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class); + viewModel.getCertDetails(context, masterKeyId, lidRank).observe(this, this::onLoadCertDetails); return root; } @@ -483,4 +490,38 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB return true; } + public static class LinkedIdViewModel extends ViewModel { + LiveData> certifyingKeysLiveData; + LiveData certDetailsLiveData; + LiveData linkedIfInfoLiveData; + + LiveData> getCertifyingKeys(Context context) { + if (certifyingKeysLiveData == null) { + certifyingKeysLiveData = new GenericLiveData<>(context, null, () -> { + KeyRepository keyRepository = KeyRepository.create(context); + return keyRepository.getAllUnifiedKeyInfoWithSecret(); + }); + } + return certifyingKeysLiveData; + } + + LiveData getCertDetails(Context context, long masterKeyId, int lidRank) { + if (certDetailsLiveData == null) { + CertificationDao certificationDao = CertificationDao.getInstance(context); + certDetailsLiveData = new GenericLiveData<>(context, null, + () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank)); + } + return certDetailsLiveData; + } + + public LiveData getLinkedIdInfo(Context context, long masterKeyId, int lidRank) { + if (linkedIfInfoLiveData == null) { + IdentityDao identityDao = IdentityDao.getInstance(context); + linkedIfInfoLiveData = new GenericLiveData<>(context, null, + () -> identityDao.getLinkedIdInfo(masterKeyId, lidRank)); + } + return linkedIfInfoLiveData; + } + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 7772202ca..dd6cdc865 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -268,6 +268,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class); viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L)); + viewModel.getUnifiedKeyInfoLiveData(getApplicationContext()).observe(this, this::onLoadUnifiedKeyInfo); if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) { OperationResult result = intent.getParcelableExtra(EXTRA_DISPLAY_RESULT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java deleted file mode 100644 index 3284448a0..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.StringRes; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.util.AttributeSet; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; - -public class CertifyKeySpinner extends KeySpinner { - private long mHiddenMasterKeyId = Constants.key.none; - private boolean mIsSingle; - - public CertifyKeySpinner(Context context) { - super(context); - } - - public CertifyKeySpinner(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CertifyKeySpinner(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void setHiddenMasterKeyId(long hiddenMasterKeyId) { - this.mHiddenMasterKeyId = hiddenMasterKeyId; - reload(); - } - - @Override - public Loader onCreateLoader(int loaderId, Bundle data) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); - - String[] projection = KeyAdapter.getProjectionWith(new String[] { - KeychainContract.KeyRings.HAS_CERTIFY_SECRET, - }); - - String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND " - + KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID - + " != " + mHiddenMasterKeyId; - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getContext(), baseUri, projection, where, null, null); - } - - private int mIndexHasCertify; - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - super.onLoadFinished(loader, data); - - if (loader.getId() == LOADER_ID) { - mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY_SECRET); - - // If: - // - no key has been pre-selected (e.g. by SageSlinger) - // - there are actually keys (not just "none" entry) - // Then: - // - select key that is capable of certifying, but only if there is only one key capable of it - if (mPreSelectedKeyId == Constants.key.none && mAdapter.getCount() > 1) { - // preselect if key can certify - int selection = -1; - while (data.moveToNext()) { - if (!data.isNull(mIndexHasCertify)) { - if (selection == -1) { - selection = data.getPosition() + 1; - mIsSingle = true; - } else { - // if selection is already set, we have more than one certify key! - // get back to "none"! - mIsSingle = false; - selection = 0; - } - } - } - setSelection(selection); - } - } - } - - public boolean isSingleEntry() { - return mIsSingle && getSelectedItemPosition() != 0; - } - - @Override - boolean isItemEnabled(Cursor cursor) { - if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { - return false; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { - return false; - } - if (cursor.isNull(mIndexHasCertify)) { - return false; - } - - // valid key - return true; - } - - @Override - public @StringRes int getNoneString() { - return R.string.choice_select_cert; - } - - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java new file mode 100644 index 000000000..a27afda34 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java @@ -0,0 +1,211 @@ +package org.sufficientlysecure.keychain.ui.widget; + + +import java.util.List; + +import android.content.Context; +import android.support.annotation.StringRes; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; + + +class KeyChoiceSpinnerAdapter extends BaseAdapter { + private Integer noneItemString; + private List data; + private final LayoutInflater layoutInflater; + + KeyChoiceSpinnerAdapter(Context context) { + super(); + + layoutInflater = LayoutInflater.from(context); + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + + public void setNoneItemString(@StringRes Integer noneItemString) { + this.noneItemString = noneItemString; + notifyDataSetChanged(); + } + + public boolean hasNoneItem() { + return noneItemString != null; + } + + @Override + public int getCount() { + return (data != null ? data.size() : 0) + (noneItemString != null ? 1 : 0); + } + + public boolean isSingleEntry() { + return data != null && data.size() == 1; + } + + @Override + public UnifiedKeyInfo getItem(int position) { + if (noneItemString != null) { + if (position == 0) { + return null; + } + position -= 1; + } + return data.get(position); + } + + @Override + public long getItemId(int position) { + if (noneItemString != null) { + if (position == 0) { + return 0; + } + position -= 1; + } + return data != null ? data.get(position).master_key_id() : 0; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getItemViewType(int position) { + if (noneItemString != null && position == 0) { + return 1; + } else { + return super.getItemViewType(position); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (noneItemString != null) { + if (position == 0) { + if (convertView != null && convertView.getTag() == null) { + return convertView; + } else { + View view = layoutInflater.inflate(R.layout.keyspinner_item_none, parent, false); + view.findViewById(R.id.keyspinner_key_name).setText(noneItemString); + return view; + } + } + } + + View view; + KeyChoiceViewHolder viewHolder; + if (convertView == null || !(convertView.getTag() instanceof KeyChoiceViewHolder)) { + view = layoutInflater.inflate(R.layout.key_list_item, parent, false); + viewHolder = new KeyChoiceViewHolder(view); + view.setTag(viewHolder); + } else { + view = convertView; + viewHolder = ((KeyChoiceViewHolder) view.getTag()); + } + + UnifiedKeyInfo keyInfo = getItem(position); + viewHolder.bind(view.getContext(), keyInfo, isEnabled(position)); + + return view; + } + + public static class KeyChoiceViewHolder { + private View mView; + private TextView mMainUserId; + private TextView mMainUserIdRest; + private TextView mCreationDate; + private ImageView mStatus; + + KeyChoiceViewHolder(View view) { + mView = view; + mMainUserId = view.findViewById(R.id.key_list_item_name); + mMainUserIdRest = view.findViewById(R.id.key_list_item_email); + mStatus = view.findViewById(R.id.key_list_item_status_icon); + mCreationDate = view.findViewById(R.id.key_list_item_creation); + } + + public void bind(Context context, UnifiedKeyInfo keyInfo, boolean enabled) { + { // set name and stuff, common to both key types + if (keyInfo.name() != null) { + mMainUserId.setText(keyInfo.name()); + } else { + mMainUserId.setText(R.string.user_id_no_name); + } + if (keyInfo.email() != null) { + mMainUserIdRest.setText(keyInfo.email()); + mMainUserIdRest.setVisibility(View.VISIBLE); + } else { + mMainUserIdRest.setVisibility(View.GONE); + } + } + + // sort of a hack: if this item isn't enabled, we make it clickable + // to intercept its click events. either way, no listener! + mView.setClickable(!enabled); + + { // set edit button and status, specific by key type + + int textColor; + + // Note: order is important! + if (keyInfo.is_revoked()) { + KeyFormattingUtils + .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); + mStatus.setVisibility(View.VISIBLE); + textColor = context.getResources().getColor(R.color.key_flag_gray); + } else if (keyInfo.is_expired()) { + KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); + mStatus.setVisibility(View.VISIBLE); + textColor = context.getResources().getColor(R.color.key_flag_gray); + } else if (!keyInfo.is_secure()) { + KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray); + mStatus.setVisibility(View.VISIBLE); + textColor = context.getResources().getColor(R.color.key_flag_gray); + } else if (keyInfo.has_any_secret()) { + mStatus.setVisibility(View.GONE); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } else { + // this is a public key - show if it's verified + if (keyInfo.is_verified()) { + KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED); + mStatus.setVisibility(View.VISIBLE); + } else { + KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED); + mStatus.setVisibility(View.VISIBLE); + } + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + mMainUserId.setTextColor(textColor); + mMainUserIdRest.setTextColor(textColor); + + if (keyInfo.has_duplicate()) { + String dateTime = DateUtils.formatDateTime(context, + keyInfo.creation() * 1000, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + mCreationDate.setText(context.getString(R.string.label_key_created, + dateTime)); + mCreationDate.setTextColor(textColor); + mCreationDate.setVisibility(View.VISIBLE); + } else { + mCreationDate.setVisibility(View.GONE); + } + } + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index d57f78d19..27be55b8a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -18,39 +18,22 @@ package org.sufficientlysecure.keychain.ui.widget; +import java.util.List; + import android.content.Context; -import android.database.Cursor; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.StringRes; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; import android.support.v7.widget.AppCompatSpinner; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.SpinnerAdapter; -import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; - - -/** - * Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon. - * Related: http://stackoverflow.com/a/27713090 - */ -public abstract class KeySpinner extends AppCompatSpinner implements - LoaderManager.LoaderCallbacks { +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +public class KeySpinner extends AppCompatSpinner { public static final String ARG_SUPER_STATE = "super_state"; public static final String ARG_KEY_ID = "key_id"; @@ -58,13 +41,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements void onKeyChanged(long masterKeyId); } - protected long mPreSelectedKeyId = Constants.key.none; - protected SelectKeyAdapter mAdapter = new SelectKeyAdapter(); + protected Long preSelectedKeyId; + protected KeyChoiceSpinnerAdapter spinnerAdapter; protected OnKeyChangedListener mListener; - // this shall note collide with other loaders inside the activity - protected int LOADER_ID = 2343; - public KeySpinner(Context context) { super(context); initView(); @@ -81,7 +61,9 @@ public abstract class KeySpinner extends AppCompatSpinner implements } private void initView() { - setAdapter(mAdapter); + spinnerAdapter = new KeyChoiceSpinnerAdapter(getContext()); + + setAdapter(spinnerAdapter); super.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { @@ -100,6 +82,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements }); } + public void setShowNone(@StringRes Integer noneStringRes) { + spinnerAdapter.setNoneItemString(noneStringRes); + } + @Override public void setOnItemSelectedListener(OnItemSelectedListener listener) { throw new UnsupportedOperationException(); @@ -109,32 +95,28 @@ public abstract class KeySpinner extends AppCompatSpinner implements mListener = listener; } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - reload(); + public void setData(List keyInfos) { + spinnerAdapter.setData(keyInfos); + maybeSelectPreselection(keyInfos); } - public void reload() { - if (getContext() instanceof FragmentActivity) { - ((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this); - } else { - // ignore, this happens during preview! we use fragmentactivities everywhere either way + private void maybeSelectPreselection(List keyInfos) { + if (preSelectedKeyId == null) { + return; + } + for (UnifiedKeyInfo keyInfo : keyInfos) { + if (keyInfo.master_key_id() == preSelectedKeyId) { + int position = keyInfos.indexOf(keyInfo); + if (spinnerAdapter.hasNoneItem()) { + position += 1; + } + setSelection(position); + } } } - @Override - public void onLoadFinished(Loader loader, Cursor data) { - if (loader.getId() == LOADER_ID) { - mAdapter.swapCursor(data); - } - } - - @Override - public void onLoaderReset(Loader loader) { - if (loader.getId() == LOADER_ID) { - mAdapter.swapCursor(null); - } + public boolean isSingleEntry() { + return spinnerAdapter.isSingleEntry(); } public long getSelectedKeyId() { @@ -143,108 +125,21 @@ public abstract class KeySpinner extends AppCompatSpinner implements } public long getSelectedKeyId(Object item) { - if (item instanceof KeyItem) { - return ((KeyItem) item).mKeyId; + if (item instanceof UnifiedKeyInfo) { + return ((UnifiedKeyInfo) item).master_key_id(); } return Constants.key.none; } public void setPreSelectedKeyId(long selectedKeyId) { - mPreSelectedKeyId = selectedKeyId; - } - - protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter { - private KeyAdapter inner; - private int mIndexMasterKeyId; - - public SelectKeyAdapter() { - inner = new KeyAdapter(getContext(), null, 0) { - - @Override - public boolean isEnabled(Cursor cursor) { - return KeySpinner.this.isItemEnabled(cursor); - } - - }; - } - - public Cursor swapCursor(Cursor newCursor) { - if (newCursor == null) return inner.swapCursor(null); - - mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID); - - Cursor oldCursor = inner.swapCursor(newCursor); - - // pre-select key if mPreSelectedKeyId is given - if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) { - do { - if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) { - setSelection(newCursor.getPosition() +1); - } - } while (newCursor.moveToNext()); - } - return oldCursor; - } - - @Override - public int getCount() { - return inner.getCount() +1; - } - - @Override - public KeyItem getItem(int position) { - if (position == 0) { - return null; - } - return inner.getItem(position -1); - } - - @Override - public long getItemId(int position) { - if (position == 0) { - return Constants.key.none; - } - return inner.getItemId(position -1); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - // Unfortunately, SpinnerAdapter does not support multiple view - // types. For this reason, we throw away convertViews of a bad - // type. This is sort of a hack, but since the number of elements - // we deal with in KeySpinners is usually very small (number of - // secret keys), this is the easiest solution. (I'm sorry.) - if (convertView != null) { - // This assumes that the inner view has non-null tags on its views! - boolean isWrongType = (convertView.getTag() == null) != (position == 0); - if (isWrongType) { - convertView = null; - } - } - - if (position > 0) { - return inner.getView(position -1, convertView, parent); - } - - View view = convertView != null ? convertView : - LayoutInflater.from(getContext()).inflate( - R.layout.keyspinner_item_none, parent, false); - ((TextView) view.findViewById(R.id.keyspinner_key_name)).setText(getNoneString()); - return view; - } - - } - - boolean isItemEnabled(Cursor cursor) { - return true; + preSelectedKeyId = selectedKeyId; } @Override public void onRestoreInstanceState(Parcelable state) { Bundle bundle = (Bundle) state; - mPreSelectedKeyId = bundle.getLong(ARG_KEY_ID); + preSelectedKeyId = bundle.getLong(ARG_KEY_ID); // restore super state super.onRestoreInstanceState(bundle.getParcelable(ARG_SUPER_STATE)); @@ -262,9 +157,4 @@ public abstract class KeySpinner extends AppCompatSpinner implements bundle.putLong(ARG_KEY_ID, getSelectedKeyId()); return bundle; } - - public @StringRes int getNoneString() { - return R.string.cert_none; - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java deleted file mode 100644 index 55b3537be..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.widget; - - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.util.AttributeSet; - -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; - -public class SignKeySpinner extends KeySpinner { - public SignKeySpinner(Context context) { - super(context); - } - - public SignKeySpinner(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public SignKeySpinner(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public Loader onCreateLoader(int loaderId, Bundle data) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); - - String[] projection = KeyAdapter.getProjectionWith(new String[] { - KeychainContract.KeyRings.HAS_SIGN_SECRET, - }); - - String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getContext(), baseUri, projection, where, null, null); - } - - private int mIndexHasSign; - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - super.onLoadFinished(loader, data); - - if (loader.getId() == LOADER_ID) { - mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN_SECRET); - } - } - - @Override - boolean isItemEnabled(Cursor cursor) { - if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { - return false; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { - return false; - } - if (cursor.getInt(KeyAdapter.INDEX_IS_SECURE) == 0) { - return false; - } - if (cursor.isNull(mIndexHasSign)) { - return false; - } - - // valid key - return true; - } - -} diff --git a/OpenKeychain/src/main/res/layout/certify_key_fragment.xml b/OpenKeychain/src/main/res/layout/certify_key_fragment.xml index d8527e408..aa7875ae9 100644 --- a/OpenKeychain/src/main/res/layout/certify_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/certify_key_fragment.xml @@ -49,7 +49,7 @@ android:paddingRight="8dp" android:gravity="center_vertical" /> - - diff --git a/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml b/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml index 14db368bf..c121cf295 100644 --- a/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml +++ b/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml @@ -132,11 +132,11 @@ android:layout_marginEnd="4dp" android:text="@string/add_keys_my_key" /> - - + From 4ac8c275f385b3c1021ec4449e3a8a867d7eb398 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 26 Jun 2018 17:30:08 +0200 Subject: [PATCH 051/124] get notification URIs from DatabaseNotificationManager --- .../keychain/provider/DatabaseNotifyManager.java | 15 +++++++++++---- .../keychain/provider/KeychainContract.java | 4 ---- .../keychain/provider/KeychainProvider.java | 2 +- .../ui/keyview/UnifiedKeyInfoViewModel.java | 5 ++++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java index 1bfb0fe65..8be6eb33a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java @@ -5,10 +5,13 @@ import android.content.ContentResolver; import android.content.Context; import android.net.Uri; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; public class DatabaseNotifyManager { + private static final Uri BASE_URI = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY); + private ContentResolver contentResolver; public static DatabaseNotifyManager create(Context context) { @@ -21,22 +24,26 @@ public class DatabaseNotifyManager { } public void notifyKeyChange(long masterKeyId) { - Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + Uri uri = getNotifyUriMasterKeyId(masterKeyId); contentResolver.notifyChange(uri, null); } public void notifyAutocryptDelete(String autocryptId, Long masterKeyId) { - Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + Uri uri = getNotifyUriMasterKeyId(masterKeyId); contentResolver.notifyChange(uri, null); } public void notifyAutocryptUpdate(String autocryptId, long masterKeyId) { - Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + Uri uri = getNotifyUriMasterKeyId(masterKeyId); contentResolver.notifyChange(uri, null); } public void notifyKeyMetadataChange(long masterKeyId) { - Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + Uri uri = getNotifyUriMasterKeyId(masterKeyId); contentResolver.notifyChange(uri, null); } + + public static Uri getNotifyUriMasterKeyId(long masterKeyId) { + return BASE_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index bba2fcd58..76ee5f6f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -125,10 +125,6 @@ public class KeychainContract { public static Uri buildUnifiedKeyRingsUri() { return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); } - - public static Uri buildGenericKeyRingUri(long masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); - } } public static class KeyRingData implements KeyRingsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 89b0e5cbb..a3d63d849 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -413,7 +413,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } if (keyId != null) { - uri = KeyRings.buildGenericKeyRingUri(keyId); + uri = DatabaseNotifyManager.getNotifyUriMasterKeyId(keyId); rowUri = uri; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java index de07b3832..abc6927fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java @@ -4,9 +4,11 @@ package org.sufficientlysecure.keychain.ui.keyview; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import android.content.Context; +import android.net.Uri; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -32,7 +34,8 @@ public class UnifiedKeyInfoViewModel extends ViewModel { } if (unifiedKeyInfoLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); - unifiedKeyInfoLiveData = new GenericLiveData<>(context, KeyRings.buildGenericKeyRingUri(masterKeyId), + Uri notifyUri = DatabaseNotifyManager.getNotifyUriMasterKeyId(masterKeyId); + unifiedKeyInfoLiveData = new GenericLiveData<>(context, notifyUri, () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); } return unifiedKeyInfoLiveData; From 353c75e49cc8d34f480fe83911fcd4f88db9d1b4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 14:51:33 +0200 Subject: [PATCH 052/124] use KeyRepository in ContactHelper --- .../keychain/util/ContactHelper.java | 126 +++++------------- 1 file changed, 35 insertions(+), 91 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index 5a57412d1..c5361a1ed 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.util; import java.io.InputStream; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.regex.Matcher; @@ -49,14 +47,12 @@ import android.util.Patterns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import timber.log.Timber; public class ContactHelper { - - private static final Map photoCache = new HashMap<>(); private final KeyRepository keyRepository; private Context mContext; @@ -396,16 +392,6 @@ public class ContactHelper { return contactName; } - private Bitmap getCachedPhotoByMasterKeyId(long masterKeyId) { - if (masterKeyId == -1) { - return null; - } - if (!photoCache.containsKey(masterKeyId)) { - photoCache.put(masterKeyId, loadPhotoByMasterKeyId(masterKeyId, false)); - } - return photoCache.get(masterKeyId); - } - public Bitmap loadPhotoByMasterKeyId(long masterKeyId, boolean highRes) { if (!isContactsPermissionGranted()) { return null; @@ -449,29 +435,6 @@ public class ContactHelper { return BitmapFactory.decodeStream(photoInputStream); } - public static final String[] KEYS_TO_CONTACT_PROJECTION = new String[]{ - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_SECRET, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.NAME, - KeychainContract.KeyRings.EMAIL, - KeychainContract.KeyRings.COMMENT }; - - public static final int INDEX_MASTER_KEY_ID = 0; - public static final int INDEX_USER_ID = 1; - public static final int INDEX_IS_EXPIRED = 2; - public static final int INDEX_IS_REVOKED = 3; - public static final int INDEX_VERIFIED = 4; - public static final int INDEX_HAS_SECRET = 5; - public static final int INDEX_HAS_ANY_SECRET = 6; - public static final int INDEX_NAME = 7; - public static final int INDEX_EMAIL = 8; - public static final int INDEX_COMMENT = 9; - /** * Write/Update the current OpenKeychain keys to the contact db */ @@ -506,35 +469,28 @@ public class ContactHelper { Set deletedKeys = getRawContactMasterKeyIds(); // Load all public Keys from OK - // TODO: figure out why using selectionArgs does not work in this case - Cursor cursor = mContentResolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), - KEYS_TO_CONTACT_PROJECTION, - KeychainContract.KeyRings.HAS_ANY_SECRET + "=0", - null, null); + for (UnifiedKeyInfo keyInfo : keyRepository.getAllUnifiedKeyInfo()) { + if (keyInfo.has_any_secret()) { + continue; + } - if (cursor != null) { - while (cursor.moveToNext()) { - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - String name = cursor.getString(INDEX_NAME); - boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; - boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0; + long masterKeyId = keyInfo.master_key_id(); + String name = keyInfo.name(); - Timber.d("masterKeyId: " + masterKeyId); + deletedKeys.remove(masterKeyId); - deletedKeys.remove(masterKeyId); + ArrayList ops = new ArrayList<>(); - ArrayList ops = new ArrayList<>(); - - // Do not store expired or revoked or unverified keys in contact db - and - // remove them if they already exist. Secret keys do not reach this point - if (isExpired || isRevoked || !isVerified) { - Timber.d("Expired or revoked or unverified: Deleting masterKeyId " - + masterKeyId); - if (masterKeyId != -1) { - deleteRawContactByMasterKeyId(masterKeyId); - } - } else if (name != null) { + // Do not store expired or revoked or unverified keys in contact db - and + // remove them if they already exist. Secret keys do not reach this point + if (keyInfo.is_expired() || keyInfo.is_revoked() || !keyInfo.is_verified()) { + Timber.d("Expired or revoked or unverified: Deleting masterKeyId " + + masterKeyId); + if (masterKeyId != -1) { + deleteRawContactByMasterKeyId(masterKeyId); + } + } else { + if (name != null) { // get raw contact to this master key id long rawContactId = findRawContactId(masterKeyId); @@ -559,7 +515,6 @@ public class ContactHelper { } } } - cursor.close(); } // Delete master key ids that are no longer present in OK @@ -581,40 +536,29 @@ public class ContactHelper { // get all keys which have associated secret keys // TODO: figure out why using selectionArgs does not work in this case - Cursor cursor = mContentResolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), - KEYS_TO_CONTACT_PROJECTION, - KeychainContract.KeyRings.HAS_ANY_SECRET + "!=0", - null, null); - if (cursor != null) try { - while (cursor.moveToNext()) { - long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0; - boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - String name = cursor.getString(INDEX_NAME); + for (UnifiedKeyInfo keyInfo : keyRepository.getAllUnifiedKeyInfoWithSecret()) { + long masterKeyId = keyInfo.master_key_id(); - if (!isExpired && !isRevoked && name != null) { - // if expired or revoked will not be removed from keysToDelete or inserted - // into main profile ("me" contact) - boolean existsInMainProfile = keysToDelete.remove(masterKeyId); - if (!existsInMainProfile) { - long rawContactId = -1;//new raw contact + if (!keyInfo.is_expired() && !keyInfo.is_revoked() && keyInfo.name() != null) { + // if expired or revoked will not be removed from keysToDelete or inserted + // into main profile ("me" contact) + boolean existsInMainProfile = keysToDelete.remove(masterKeyId); + if (!existsInMainProfile) { + long rawContactId = -1;//new raw contact - Timber.d("masterKeyId with secret " + masterKeyId); + Timber.d("masterKeyId with secret " + masterKeyId); - ArrayList ops = new ArrayList<>(); - insertMainProfileRawContact(ops, masterKeyId); - writeContactKey(ops, rawContactId, masterKeyId, name); + ArrayList ops = new ArrayList<>(); + insertMainProfileRawContact(ops, masterKeyId); + writeContactKey(ops, rawContactId, masterKeyId, keyInfo.name()); - try { - mContentResolver.applyBatch(ContactsContract.AUTHORITY, ops); - } catch (Exception e) { - Timber.w(e); - } + try { + mContentResolver.applyBatch(ContactsContract.AUTHORITY, ops); + } catch (Exception e) { + Timber.w(e); } } } - } finally { - cursor.close(); } for (long masterKeyId : keysToDelete) { From 375dd39ed1178c9d795b281db7bb8b4e91795bf4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 14:59:22 +0200 Subject: [PATCH 053/124] use LiveData to load data in TransferFragment --- .../ui/transfer/loader/SecretKeyLoader.java | 121 ------------------ .../transfer/presenter/TransferPresenter.java | 69 +++++----- .../ui/transfer/view/TransferFragment.java | 5 +- .../transfer/view/TransferSecretKeyList.java | 46 +++---- 4 files changed, 52 insertions(+), 189 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/loader/SecretKeyLoader.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/loader/SecretKeyLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/loader/SecretKeyLoader.java deleted file mode 100644 index 444cd920d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/loader/SecretKeyLoader.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.transfer.loader; - - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.support.v4.content.AsyncTaskLoader; -import android.util.Log; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.transfer.loader.SecretKeyLoader.SecretKeyItem; -import timber.log.Timber; - - -public class SecretKeyLoader extends AsyncTaskLoader> { - public static final String[] PROJECTION = new String[] { - KeyRings.MASTER_KEY_ID, - KeyRings.CREATION, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.HAS_ANY_SECRET - }; - private static final int INDEX_KEY_ID = 0; - private static final int INDEX_CREATION = 1; - private static final int INDEX_NAME = 2; - private static final int INDEX_EMAIL = 3; - - - private final ContentResolver contentResolver; - - private List cachedResult; - - - public SecretKeyLoader(Context context, ContentResolver contentResolver) { - super(context); - - this.contentResolver = contentResolver; - } - - @Override - public List loadInBackground() { - String where = KeyRings.HAS_ANY_SECRET + " = 1"; - Cursor cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), PROJECTION, where, null, null); - if (cursor == null) { - Timber.e("Error loading key items!"); - return null; - } - - try { - ArrayList secretKeyItems = new ArrayList<>(); - while (cursor.moveToNext()) { - SecretKeyItem secretKeyItem = new SecretKeyItem(cursor); - secretKeyItems.add(secretKeyItem); - } - - return Collections.unmodifiableList(secretKeyItems); - } finally { - cursor.close(); - } - } - - @Override - public void deliverResult(List keySubkeyStatus) { - cachedResult = keySubkeyStatus; - - if (isStarted()) { - super.deliverResult(keySubkeyStatus); - } - } - - @Override - protected void onStartLoading() { - if (cachedResult != null) { - deliverResult(cachedResult); - } - - if (takeContentChanged() || cachedResult == null) { - forceLoad(); - } - } - - public static class SecretKeyItem { - final int position; - public final long masterKeyId; - public final long creationMillis; - public final String name; - public final String email; - - SecretKeyItem(Cursor cursor) { - position = cursor.getPosition(); - - masterKeyId = cursor.getLong(INDEX_KEY_ID); - creationMillis = cursor.getLong(INDEX_CREATION) * 1000; - - name = cursor.getString(INDEX_NAME); - email = cursor.getString(INDEX_EMAIL); - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java index 52a074933..32935fa33 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.List; +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; import android.content.Context; import android.graphics.Bitmap; import android.net.ConnectivityManager; @@ -30,19 +32,16 @@ import android.net.Uri; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build.VERSION_CODES; -import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.support.annotation.RequiresApi; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; import android.support.v7.widget.RecyclerView.Adapter; import android.view.LayoutInflater; import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.network.KeyTransferInteractor; import org.sufficientlysecure.keychain.network.KeyTransferInteractor.KeyTransferCallback; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; @@ -54,7 +53,7 @@ import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.Callback; -import org.sufficientlysecure.keychain.ui.transfer.loader.SecretKeyLoader.SecretKeyItem; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.transfer.view.ReceivedSecretKeyList.OnClickImportKeyListener; import org.sufficientlysecure.keychain.ui.transfer.view.ReceivedSecretKeyList.ReceivedKeyAdapter; import org.sufficientlysecure.keychain.ui.transfer.view.ReceivedSecretKeyList.ReceivedKeyItem; @@ -65,20 +64,19 @@ import timber.log.Timber; @RequiresApi(api = VERSION_CODES.LOLLIPOP) -public class TransferPresenter implements KeyTransferCallback, LoaderCallbacks>, - OnClickTransferKeyListener, OnClickImportKeyListener { +public class TransferPresenter implements KeyTransferCallback, OnClickTransferKeyListener, OnClickImportKeyListener { private static final String DELIMITER_START = "-----BEGIN PGP PRIVATE KEY BLOCK-----"; private static final String DELIMITER_END = "-----END PGP PRIVATE KEY BLOCK-----"; private static final String BACKSTACK_TAG_TRANSFER = "transfer"; private final Context context; private final TransferMvpView view; - private final LoaderManager loaderManager; - private final int loaderId; - private final KeyRepository databaseInteractor; + private final KeyRepository keyRepository; private final TransferKeyAdapter secretKeyAdapter; private final ReceivedKeyAdapter receivedKeyAdapter; + private final LifecycleOwner lifecycleOwner; + private final GenericViewModel viewModel; private KeyTransferInteractor keyTransferClientInteractor; @@ -90,12 +88,13 @@ public class TransferPresenter implements KeyTransferCallback, LoaderCallbacks> liveData = + viewModel.getGenericLiveData(context, keyRepository::getAllUnifiedKeyInfoWithSecret); + liveData.observe(lifecycleOwner, this::onLoadSecretUnifiedKeyInfo); if (keyTransferServerInteractor == null && keyTransferClientInteractor == null && !wasConnected) { checkWifiResetAndStartListen(); } } + private void onLoadSecretUnifiedKeyInfo(List data) { + secretKeyAdapter.setData(data); + view.setShowSecretKeyEmptyView(data.isEmpty()); + } + public void onUiStop() { connectionClear(); @@ -288,12 +294,9 @@ public class TransferPresenter implements KeyTransferCallback, LoaderCallbacks { + secretKeyAdapter.focusItem(null); + secretKeyAdapter.addToFinishedItems(masterKeyId); }, 750); } @@ -376,6 +379,10 @@ public class TransferPresenter implements KeyTransferCallback, LoaderCallbacks> onCreateLoader(int id, Bundle args) { - return secretKeyAdapter.createLoader(context); - } - - @Override - public void onLoadFinished(Loader> loader, List data) { - secretKeyAdapter.setData(data); - view.setShowSecretKeyEmptyView(data.isEmpty()); - } - - @Override - public void onLoaderReset(Loader> loader) { - secretKeyAdapter.setData(null); - } - - public interface TransferMvpView { void showNotOnWifi(); void showWaitingForConnection(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java index b9eb8f7bc..263205da7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.transfer.view; import android.app.Activity; +import android.arch.lifecycle.ViewModelProviders; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -62,6 +63,7 @@ import org.sufficientlysecure.keychain.ui.MainActivity; import org.sufficientlysecure.keychain.ui.QrCodeCaptureActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.Callback; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter; import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter.TransferMvpView; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -140,7 +142,8 @@ public class TransferFragment extends Fragment implements TransferMvpView { } }); - presenter = new TransferPresenter(getContext(), getLoaderManager(), LOADER_ID, this); + GenericViewModel genericViewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + presenter = new TransferPresenter(getContext(), this, genericViewModel, this); setHasOptionsMenu(true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferSecretKeyList.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferSecretKeyList.java index 6bca7cd05..5cdcd8842 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferSecretKeyList.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferSecretKeyList.java @@ -22,8 +22,8 @@ import java.util.ArrayList; import java.util.List; import android.content.Context; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.content.Loader; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; @@ -35,8 +35,7 @@ import android.widget.TextView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.transfer.loader.SecretKeyLoader; -import org.sufficientlysecure.keychain.ui.transfer.loader.SecretKeyLoader.SecretKeyItem; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; @@ -75,7 +74,7 @@ public class TransferSecretKeyList extends RecyclerView { private final OnClickTransferKeyListener onClickTransferKeyListener; private Long focusedMasterKeyId; - private List data; + private List data; private ArrayList finishedItems = new ArrayList<>(); private boolean allItemsDisabled; @@ -87,15 +86,16 @@ public class TransferSecretKeyList extends RecyclerView { this.onClickTransferKeyListener = onClickTransferKeyListener; } + @NonNull @Override - public TransferKeyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public TransferKeyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new TransferKeyViewHolder(layoutInflater.inflate(R.layout.key_transfer_item, parent, false)); } @Override - public void onBindViewHolder(TransferKeyViewHolder holder, int position) { - SecretKeyItem item = data.get(position); - boolean isFinished = finishedItems.contains(item.masterKeyId); + public void onBindViewHolder(@NonNull TransferKeyViewHolder holder, int position) { + UnifiedKeyInfo item = data.get(position); + boolean isFinished = finishedItems.contains(item.master_key_id()); holder.bind(context, item, onClickTransferKeyListener, focusedMasterKeyId, isFinished, allItemsDisabled); } @@ -106,10 +106,10 @@ public class TransferSecretKeyList extends RecyclerView { @Override public long getItemId(int position) { - return data.get(position).masterKeyId; + return data.get(position).master_key_id(); } - public void setData(List data) { + public void setData(List data) { this.data = data; notifyDataSetChanged(); } @@ -129,10 +129,6 @@ public class TransferSecretKeyList extends RecyclerView { notifyItemRangeChanged(0, getItemCount()); } - public Loader> createLoader(Context context) { - return new SecretKeyLoader(context, context.getContentResolver()); - } - public void setAllDisabled(boolean allItemsdisablde) { allItemsDisabled = allItemsdisablde; notifyItemRangeChanged(0, getItemCount()); @@ -157,23 +153,23 @@ public class TransferSecretKeyList extends RecyclerView { vState = itemView.findViewById(R.id.transfer_state); } - private void bind(Context context, final SecretKeyItem item, + private void bind(Context context, UnifiedKeyInfo item, final OnClickTransferKeyListener onClickTransferKeyListener, Long focusedMasterKeyId, boolean isFinished, boolean disableAll) { - if (item.name != null) { - vName.setText(item.name); + if (item.name() != null) { + vName.setText(item.name()); vName.setVisibility(View.VISIBLE); } else { vName.setVisibility(View.GONE); } - if (item.email != null) { - vEmail.setText(item.email); + if (item.email() != null) { + vEmail.setText(item.email()); vEmail.setVisibility(View.VISIBLE); } else { vEmail.setVisibility(View.GONE); } - String dateTime = DateUtils.formatDateTime(context, item.creationMillis, + String dateTime = DateUtils.formatDateTime(context, item.creation() * 1000, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); @@ -186,7 +182,7 @@ public class TransferSecretKeyList extends RecyclerView { } if (focusedMasterKeyId != null) { - if (focusedMasterKeyId != item.masterKeyId) { + if (focusedMasterKeyId != item.master_key_id()) { itemView.animate().alpha(0.2f).start(); vState.setDisplayedChild(isFinished ? STATE_TRANSFERRED : STATE_INVISIBLE); } else { @@ -199,12 +195,8 @@ public class TransferSecretKeyList extends RecyclerView { } if (focusedMasterKeyId == null && onClickTransferKeyListener != null) { - vSendButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onClickTransferKeyListener.onUiClickTransferKey(item.masterKeyId); - } - }); + vSendButton.setOnClickListener( + v -> onClickTransferKeyListener.onUiClickTransferKey(item.master_key_id())); } else { vSendButton.setOnClickListener(null); } From dbc1e5f52304e1e9600f58f79c3fcb632b675f36 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 15:12:40 +0200 Subject: [PATCH 054/124] use KeyRepository in BackupRestoreFragment --- .../keychain/ui/BackupRestoreFragment.java | 101 +++++++----------- 1 file changed, 40 insertions(+), 61 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java index 84c2882d7..d8fa88491 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java @@ -22,12 +22,11 @@ import java.util.ArrayList; import java.util.Iterator; import android.app.Activity; -import android.content.ContentResolver; import android.content.Intent; -import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; @@ -38,9 +37,10 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; @@ -54,33 +54,16 @@ public class BackupRestoreFragment extends Fragment { private static final int REQUEST_CODE_INPUT = 0x00007003; @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.backup_restore_fragment, container, false); View backupAll = view.findViewById(R.id.backup_all); View backupPublicKeys = view.findViewById(R.id.backup_public_keys); final View restore = view.findViewById(R.id.restore); - backupAll.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - exportToFile(true); - } - }); - - backupPublicKeys.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - exportToFile(false); - } - }); - - restore.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - restore(); - } - }); + backupAll.setOnClickListener(v -> exportToFile(true)); + backupPublicKeys.setOnClickListener(v -> exportToFile(false)); + restore.setOnClickListener(v -> restore()); return view; } @@ -96,50 +79,46 @@ public class BackupRestoreFragment extends Fragment { return; } - new AsyncTask>>() { + KeyRepository keyRepository = KeyRepository.create(requireContext()); + + // This can probably be optimized quite a bit. + // Typically there are only few secret keys though, so it doesn't really matter. + + new AsyncTask>>() { @Override - protected ArrayList> doInBackground(ContentResolver... resolver) { + protected ArrayList> doInBackground(Void... ignored) { + KeyRepository keyRepository = KeyRepository.create(requireContext()); ArrayList> askPassphraseIds = new ArrayList<>(); - Cursor cursor = resolver[0].query( - KeyRings.buildUnifiedKeyRingsUri(), new String[]{ - KeyRings.MASTER_KEY_ID, - KeyRings.HAS_SECRET, - }, KeyRings.HAS_SECRET + " != 0", null, null); - try { - if (cursor != null) { - while (cursor.moveToNext()) { - SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); - switch (secretKeyType) { - // all of these make no sense to ask - case PASSPHRASE_EMPTY: - case DIVERT_TO_CARD: - case UNAVAILABLE: - continue; - case GNU_DUMMY: { - Long masterKeyId = cursor.getLong(0); - Long subKeyId = getFirstSubKeyWithPassphrase(masterKeyId, resolver[0]); - if(subKeyId != null) { - askPassphraseIds.add(new Pair<>(masterKeyId, subKeyId)); - } - continue; - } - default: { - long masterKeyId = cursor.getLong(0); - askPassphraseIds.add(new Pair<>(masterKeyId, masterKeyId)); - } - } - } + for (UnifiedKeyInfo keyInfo : keyRepository.getAllUnifiedKeyInfoWithSecret()) { + long masterKeyId = keyInfo.master_key_id(); + SecretKeyType secretKeyType; + try { + secretKeyType = keyRepository.getSecretKeyType(keyInfo.master_key_id()); + } catch (NotFoundException e) { + throw new IllegalStateException("Error: no secret key type for secret key!"); } - } finally { - if (cursor != null) { - cursor.close(); + switch (secretKeyType) { + // all of these make no sense to ask + case PASSPHRASE_EMPTY: + case DIVERT_TO_CARD: + case UNAVAILABLE: + continue; + case GNU_DUMMY: { + Long subKeyId = getFirstSubKeyWithPassphrase(masterKeyId); + if(subKeyId != null) { + askPassphraseIds.add(new Pair<>(masterKeyId, subKeyId)); + } + continue; + } + default: { + askPassphraseIds.add(new Pair<>(masterKeyId, masterKeyId)); + } } } return askPassphraseIds; } - private Long getFirstSubKeyWithPassphrase(long masterKeyId, ContentResolver resolver) { - KeyRepository keyRepository = KeyRepository.create(requireContext()); + private Long getFirstSubKeyWithPassphrase(long masterKeyId) { for (SubKey subKey : keyRepository.getSubKeysByMasterKeyId(masterKeyId)) { switch (subKey.has_secret()) { case PASSPHRASE_EMPTY: @@ -174,7 +153,7 @@ public class BackupRestoreFragment extends Fragment { startBackup(true); } - }.execute(activity.getContentResolver()); + }.execute(); } private void startPassphraseActivity() { From 9ea36286dca4086942818f68e04bf22721ca43c1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 19:26:09 +0200 Subject: [PATCH 055/124] use FlexibleAdapter for new KeyChoiceAdapter --- .../AppSettingsAllowedKeysListFragment.java | 134 +++------- .../ui/SelectSignKeyIdListFragment.java | 3 - .../ui/dialog/RemoteDeduplicateActivity.java | 34 +-- .../ui/dialog/RemoteDeduplicatePresenter.java | 64 ++--- .../keychain/ui/adapter/KeyChoiceAdapter.java | 234 +++++++++++++----- .../res/layout/api_remote_deduplicate.xml | 2 +- ...icate_key_item.xml => key_choice_item.xml} | 34 ++- 7 files changed, 247 insertions(+), 258 deletions(-) rename OpenKeychain/src/main/res/layout/{duplicate_key_item.xml => key_choice_item.xml} (57%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index b08d701ca..ea2a03c17 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -18,45 +18,36 @@ package org.sufficientlysecure.keychain.remote.ui; +import java.util.List; import java.util.Set; -import android.database.Cursor; -import android.net.Uri; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; -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 android.support.v7.widget.LinearLayoutManager; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; -import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter; -import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; +import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; -public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { +public class AppSettingsAllowedKeysListFragment extends RecyclerFragment { private static final String ARG_PACKAGE_NAME = "package_name"; - private KeySelectableAdapter mAdapter; - private ApiAppDao mApiAppDao; + private KeyChoiceAdapter keyChoiceAdapter; + private ApiAppDao apiAppDao; private String packageName; - /** - * Creates new instance of this fragment - */ public static AppSettingsAllowedKeysListFragment newInstance(String packageName) { AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment(); + Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, packageName); - frag.setArguments(args); return frag; @@ -66,34 +57,9 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mApiAppDao = ApiAppDao.getInstance(getActivity()); + apiAppDao = ApiAppDao.getInstance(getActivity()); } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View layout = super.onCreateView(inflater, container, savedInstanceState); - ListView lv = layout.findViewById(android.R.id.list); - ViewGroup parent = (ViewGroup) lv.getParent(); - - /* - * http://stackoverflow.com/a/15880684 - * Remove ListView and add FixedListView in its place. - * This is done here programatically to be still able to use the progressBar of ListFragment. - * - * We want FixedListView to be able to put this ListFragment inside a ScrollView - */ - int lvIndex = parent.indexOfChild(lv); - parent.removeViewAt(lvIndex); - FixedListView newLv = new FixedListView(getActivity()); - newLv.setId(android.R.id.list); - parent.addView(newLv, lvIndex, lv.getLayoutParams()); - return layout; - } - - /** - * Define Adapter and Loader on create of Activity - */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -104,70 +70,36 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - Set checked = mApiAppDao.getAllowedKeyIdsForApp(packageName); - mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked); - setListAdapter(mAdapter); - getListView().setOnItemClickListener(mAdapter); + getRecyclerView().setLayoutManager(new LinearLayoutManager(requireContext())); // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); + hideList(false); + KeyRepository keyRepository = KeyRepository.create(requireContext()); + GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + LiveData> liveData = + viewModel.getGenericLiveData(requireContext(), keyRepository::getAllUnifiedKeyInfoWithSecret); + liveData.observe(this, this::onLoadUnifiedKeyData); } - /** Returns all selected master key ids. */ - public Set getSelectedMasterKeyIds() { - return mAdapter.getSelectedMasterKeyIds(); - } - - /** Returns all selected user ids. - public String[] getSelectedUserIds() { - Vector userIds = new Vector<>(); - for (int i = 0; i < getListView().getCount(); ++i) { - if (getListView().isItemChecked(i)) { - userIds.add(spinnerAdapter.getUserId(i)); - } - } - - // make empty array to not return null - String userIdArray[] = new String[0]; - return userIds.toArray(userIdArray); - } */ public void saveAllowedKeys() { - mApiAppDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds()); + Set longs = keyChoiceAdapter.getSelectionIds(); + apiAppDao.saveAllowedKeyIdsForApp(packageName, longs); } - @Override - public Loader onCreateLoader(int loaderId, Bundle data) { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri(); - String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1"; - - return new CursorLoader(getActivity(), baseUri, KeyAdapter.PROJECTION, where, null, null); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.swapCursor(data); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); + public void onLoadUnifiedKeyData(List data) { + if (keyChoiceAdapter == null) { + keyChoiceAdapter = new KeyChoiceAdapter(true, data); + setAdapter(keyChoiceAdapter); } else { - setListShownNoAnimation(true); + keyChoiceAdapter.setUnifiedKeyInfoItems(data); } - } - @Override - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - mAdapter.swapCursor(null); + Set checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); + keyChoiceAdapter.setSelectionByIds(checkedIds); + + boolean animateShowList = !isResumed(); + showList(animateShowList); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 3f8c4b905..38110e624 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -52,9 +52,6 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment data) { - keyChoiceAdapter.setData(data); - } - - @Override - public void setActiveItem(Integer position) { - keyChoiceAdapter.setActiveItem(position); - } - - @Override - public void setEnableSelectButton(boolean enabled) { - buttonSelect.setEnabled(enabled); + public void setKeyListAdapter(Adapter adapter) { + keyChoiceList.setAdapter(adapter); } }; } @@ -252,8 +236,6 @@ public class RemoteDeduplicateActivity extends FragmentActivity { private void setupListenersForPresenter() { buttonSelect.setOnClickListener(view -> presenter.onClickSelect()); buttonCancel.setOnClickListener(view -> presenter.onClickCancel()); - keyChoiceList.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), - (view, position) -> presenter.onKeyItemClick(position))); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index fd8a7ebae..d51a5b0a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -22,19 +22,15 @@ import java.util.List; import android.arch.lifecycle.LifecycleOwner; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView.Adapter; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.remote.AutocryptInteractor; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicateActivity.DeduplicateViewModel; -import timber.log.Timber; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; class RemoteDeduplicatePresenter { - private final PackageManager packageManager; private final Context context; private final LifecycleOwner lifecycleOwner; @@ -43,15 +39,12 @@ class RemoteDeduplicatePresenter { private DeduplicateViewModel viewModel; private RemoteDeduplicateView view; - private Integer selectedItem; - private List keyInfoData; + private KeyChoiceAdapter keyChoiceAdapter; RemoteDeduplicatePresenter(Context context, LifecycleOwner lifecycleOwner) { this.context = context; this.lifecycleOwner = lifecycleOwner; - - packageManager = context.getPackageManager(); } public void setView(RemoteDeduplicateView view) { @@ -62,42 +55,27 @@ class RemoteDeduplicatePresenter { this.viewModel = viewModel; this.autocryptInteractor = AutocryptInteractor.getInstance(context, viewModel.getPackageName()); - try { - setPackageInfo(viewModel.getPackageName()); - } catch (NameNotFoundException e) { - Timber.e("Unable to find info of calling app!"); - view.finishAsCancelled(); - return; - } - view.setAddressText(viewModel.getDuplicateAddress()); viewModel.getKeyInfoLiveData(context).observe(lifecycleOwner, this::onLoadKeyInfos); } - private void setPackageInfo(String packageName) throws NameNotFoundException { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0); - Drawable appIcon = packageManager.getApplicationIcon(applicationInfo); - - view.setTitleClientIcon(appIcon); - } - private void onLoadKeyInfos(List data) { - this.keyInfoData = data; - view.setKeyListData(data); + if (keyChoiceAdapter == null) { + keyChoiceAdapter = new KeyChoiceAdapter(false, data); + view.setKeyListAdapter(keyChoiceAdapter); + } else { + keyChoiceAdapter.setUnifiedKeyInfoItems(data); + } } void onClickSelect() { - if (keyInfoData == null) { - Timber.e("got click on select with no data…?"); + UnifiedKeyInfo activeItem = keyChoiceAdapter.getActiveItem(); + if (activeItem == null) { + view.showNoSelectionError(); return; } - if (selectedItem == null) { - Timber.e("got click on select with no selection…?"); - return; - } - - long masterKeyId = keyInfoData.get(selectedItem).master_key_id(); + long masterKeyId = activeItem.master_key_id(); autocryptInteractor.updateKeyGossipFromDedup(viewModel.getDuplicateAddress(), masterKeyId); view.finish(); @@ -111,25 +89,13 @@ class RemoteDeduplicatePresenter { view.finishAsCancelled(); } - void onKeyItemClick(int position) { - if (selectedItem != null && position == selectedItem) { - selectedItem = null; - } else { - selectedItem = position; - } - view.setActiveItem(selectedItem); - view.setEnableSelectButton(selectedItem != null); - } - interface RemoteDeduplicateView { + void showNoSelectionError(); void finish(); void finishAsCancelled(); void setAddressText(String text); - void setTitleClientIcon(Drawable drawable); - void setKeyListData(List data); - void setActiveItem(Integer position); - void setEnableSelectButton(boolean enabled); + void setKeyListAdapter(Adapter adapter); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 1448e9f31..d495cb4e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -1,81 +1,66 @@ package org.sufficientlysecure.keychain.ui.adapter; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Drawable.ConstantState; -import android.support.annotation.NonNull; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.Adapter; import android.text.format.DateUtils; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; +import android.widget.CheckBox; +import android.widget.RadioButton; import android.widget.TextView; +import eu.davidea.flexibleadapter.FlexibleAdapter; +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; +import eu.davidea.flexibleadapter.items.IFlexible; +import eu.davidea.viewholders.FlexibleViewHolder; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceViewHolder; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceItem; -public class KeyChoiceAdapter extends Adapter { - private final LayoutInflater layoutInflater; - private final Resources resources; - private List data; - private Drawable iconUnselected; - private Drawable iconSelected; +public class KeyChoiceAdapter extends FlexibleAdapter { private Integer activeItem; - public KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { - this.layoutInflater = layoutInflater; - this.resources = resources; + public KeyChoiceAdapter(boolean isMultiChoice, List items) { + super(getKeyChoiceItems(items)); + setMode(isMultiChoice ? Mode.MULTI : Mode.SINGLE); + addListener((OnItemClickListener) (view, position) -> onClickItem(position)); } - @NonNull - @Override - public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View keyChoiceItemView = layoutInflater.inflate(R.layout.duplicate_key_item, parent, false); - return new KeyChoiceViewHolder(keyChoiceItemView); - } - - @Override - public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { - UnifiedKeyInfo keyInfo = data.get(position); - Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; - holder.bind(keyInfo, icon); - } - - @Override - public int getItemCount() { - return data != null ? data.size() : 0; - } - - public void setData(List data) { - this.data = data; - notifyDataSetChanged(); - } - - public void setSelectionDrawable(Drawable drawable) { - ConstantState constantState = drawable.getConstantState(); - if (constantState == null) { - return; + @Nullable + private static ArrayList getKeyChoiceItems(@Nullable List items) { + if (items == null) { + return null; } + ArrayList choiceItems = new ArrayList<>(); + for (UnifiedKeyInfo keyInfo : items) { + KeyChoiceItem keyChoiceItem = new KeyChoiceItem(keyInfo); + choiceItems.add(keyChoiceItem); + } + return choiceItems; + } - iconSelected = constantState.newDrawable(resources); - - iconUnselected = constantState.newDrawable(resources); - DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); - - notifyDataSetChanged(); + private boolean onClickItem(int position) { + if (getMode() == Mode.MULTI) { + toggleSelection(position); + notifyItemChanged(position); + } else { + setActiveItem(position); + } + return true; } public void setActiveItem(Integer newActiveItem) { + if (getMode() != Mode.SINGLE) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + Integer prevActiveItem = this.activeItem; this.activeItem = newActiveItem; @@ -87,20 +72,117 @@ public class KeyChoiceAdapter extends Adapter { } } - public static class KeyChoiceViewHolder extends RecyclerView.ViewHolder { - private final TextView vName; - private final TextView vCreation; - private final ImageView vIcon; - - KeyChoiceViewHolder(View itemView) { - super(itemView); - - vName = itemView.findViewById(R.id.key_list_item_name); - vCreation = itemView.findViewById(R.id.key_list_item_creation); - vIcon = itemView.findViewById(R.id.key_list_item_icon); + public UnifiedKeyInfo getActiveItem() { + if (getMode() != Mode.SINGLE) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + if (activeItem == null) { + return null; } - void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { + KeyChoiceItem item = getItem(activeItem); + return item == null ? null : item.keyInfo; + } + + public void setUnifiedKeyInfoItems(List keyInfos) { + List keyChoiceItems = getKeyChoiceItems(keyInfos); + updateDataSet(keyChoiceItems); + } + + @Override + public long getItemId(int position) { + KeyChoiceItem item = getItem(position); + if (item == null) { + return RecyclerView.NO_ID; + } + return item.getMasterKeyId(); + } + + public void setSelectionByIds(Set checkedIds) { + if (getMode() != Mode.MULTI) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + + clearSelection(); + for (int position = 0; position < getItemCount(); position++) { + long itemId = getItemId(position); + if (checkedIds.contains(itemId)) { + addSelection(position); + } + } + } + + public Set getSelectionIds() { + if (getMode() != Mode.MULTI) { + throw new IllegalStateException("Cannot get active item in single select mode!"); + } + + Set result = new HashSet<>(); + for (int position : getSelectedPositions()) { + long itemId = getItemId(position); + result.add(itemId); + } + return result; + } + + public static class KeyChoiceItem extends AbstractFlexibleItem { + private UnifiedKeyInfo keyInfo; + + KeyChoiceItem(UnifiedKeyInfo keyInfo) { + this.keyInfo = keyInfo; + setSelectable(true); + } + + @Override + public int getLayoutRes() { + return R.layout.key_choice_item; + } + + @Override + public KeyChoiceViewHolder createViewHolder(View view, FlexibleAdapter adapter) { + return new KeyChoiceViewHolder(view, adapter); + } + + @Override + public void bindViewHolder(FlexibleAdapter adapter, KeyChoiceViewHolder holder, int position, + List payloads) { + boolean isActive = adapter.isSelected(position); + holder.bind(keyInfo, adapter.getMode(), isActive); + } + + @Override + public boolean equals(Object o) { + return (o instanceof KeyChoiceItem) && + ((KeyChoiceItem) o).keyInfo.master_key_id() == keyInfo.master_key_id(); + } + + @Override + public int hashCode() { + long masterKeyId = keyInfo.master_key_id(); + return (int) (masterKeyId ^ (masterKeyId >>> 32)); + } + + public long getMasterKeyId() { + return keyInfo.master_key_id(); + } + } + + public static class KeyChoiceViewHolder extends FlexibleViewHolder { + private final TextView vName; + private final TextView vCreation; + private final CheckBox vCheckbox; + private final RadioButton vRadio; + + KeyChoiceViewHolder(View itemView, FlexibleAdapter adapter) { + super(itemView, adapter); + + vName = itemView.findViewById(R.id.text_keychoice_name); + vCreation = itemView.findViewById(R.id.text_keychoice_creation); + vCheckbox = itemView.findViewById(R.id.checkbox_keychoice); + vRadio = itemView.findViewById(R.id.radio_keychoice); + } + + void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive) { vName.setText(keyInfo.name()); Context context = vCreation.getContext(); @@ -109,7 +191,25 @@ public class KeyChoiceAdapter extends Adapter { DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); - vIcon.setImageDrawable(selectionIcon); + switch (choiceMode) { + case Mode.IDLE: { + vRadio.setVisibility(View.GONE); + vCheckbox.setVisibility(View.GONE); + break; + } + case Mode.SINGLE: { + vRadio.setVisibility(View.VISIBLE); + vRadio.setChecked(isActive); + vCheckbox.setVisibility(View.GONE); + break; + } + case Mode.MULTI: { + vCheckbox.setVisibility(View.VISIBLE); + vCheckbox.setChecked(isActive); + vRadio.setVisibility(View.GONE); + break; + } + } } } } diff --git a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml index 3621f5c5d..d2b1461b4 100644 --- a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml +++ b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml @@ -101,7 +101,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/duplicate_key_list" - tools:listitem="@layout/duplicate_key_item" + tools:listitem="@layout/key_choice_item" tools:layout_height="100dp" /> diff --git a/OpenKeychain/src/main/res/layout/duplicate_key_item.xml b/OpenKeychain/src/main/res/layout/key_choice_item.xml similarity index 57% rename from OpenKeychain/src/main/res/layout/duplicate_key_item.xml rename to OpenKeychain/src/main/res/layout/key_choice_item.xml index 5e8ed1477..f808b08ab 100644 --- a/OpenKeychain/src/main/res/layout/duplicate_key_item.xml +++ b/OpenKeychain/src/main/res/layout/key_choice_item.xml @@ -8,30 +8,42 @@ android:gravity="center_vertical" android:orientation="horizontal"> - + + + android:orientation="vertical" + android:layout_marginStart="12dp" + android:layout_marginLeft="12dp" + > Date: Wed, 27 Jun 2018 22:20:42 +0200 Subject: [PATCH 056/124] use KeyChoiceAdapter in SelectPublicKeyFragment --- .../AppSettingsAllowedKeysListFragment.java | 5 +- .../remote/ui/SelectPublicKeyFragment.java | 289 +++++------------- 2 files changed, 77 insertions(+), 217 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index ea2a03c17..4206d458f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -91,13 +91,12 @@ public class AppSettingsAllowedKeysListFragment extends RecyclerFragment checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); + keyChoiceAdapter.setSelectionByIds(checkedIds); } else { keyChoiceAdapter.setUnifiedKeyInfoItems(data); } - Set checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); - keyChoiceAdapter.setSelectionByIds(checkedIds); - boolean animateShowList = !isResumed(); showList(animateShowList); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index febeb4e8e..847321f16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -17,49 +17,33 @@ package org.sufficientlysecure.keychain.remote.ui; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; +import android.support.annotation.NonNull; import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.remote.ui.adapter.SelectEncryptKeyAdapter; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; -public class SelectPublicKeyFragment extends RecyclerFragment - implements TextWatcher, LoaderManager.LoaderCallbacks { +public class SelectPublicKeyFragment extends RecyclerFragment { public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; -// private EditText mSearchView; - private long mSelectedMasterKeyIds[]; - private String mQuery; + private Set selectedMasterKeyIds; + private KeyChoiceAdapter keyChoiceAdapter; + private KeyRepository keyRepository; - /** - * Creates new instance of this fragment - */ public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) { SelectPublicKeyFragment frag = new SelectPublicKeyFragment(); Bundle args = new Bundle(); @@ -73,203 +57,80 @@ public class SelectPublicKeyFragment extends RecyclerFragment(); + for (long preselectedKey : getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS)) { + selectedMasterKeyIds.add(preselectedKey); + } } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final Context context = getContext(); - FrameLayout root = new FrameLayout(context); - - LinearLayout progressContainer = new LinearLayout(context); - progressContainer.setId(INTERNAL_PROGRESS_CONTAINER_ID); - progressContainer.setOrientation(LinearLayout.VERTICAL); - progressContainer.setGravity(Gravity.CENTER); - progressContainer.setVisibility(View.GONE); - - ProgressBar progressBar = new ProgressBar(context, null, - android.R.attr.progressBarStyleLarge); - - progressContainer.addView(progressBar, new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - root.addView(progressContainer, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - FrameLayout listContainer = new FrameLayout(context); - listContainer.setId(INTERNAL_LIST_CONTAINER_ID); - - TextView textView = new TextView(context); - textView.setId(INTERNAL_EMPTY_VIEW_ID); - textView.setGravity(Gravity.CENTER); - - listContainer.addView(textView, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - LinearLayout innerListContainer = new LinearLayout(context); - innerListContainer.setOrientation(LinearLayout.VERTICAL); - - // TODO: search is broken: When searching, previously selected keys are no longer selected -// mSearchView = new EditText(context); -// mSearchView.setId(android.R.id.input); -// mSearchView.setHint(R.string.menu_search); -// mSearchView.setCompoundDrawablesWithIntrinsicBounds( -// ContextCompat.getDrawable( -// context, -// R.drawable.ic_search_grey_24dp -// ), null, null, null); -// -// innerListContainer.addView(mSearchView, new LinearLayout.LayoutParams( -// ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - RecyclerView listView = new RecyclerView(context); - listView.setId(INTERNAL_LIST_VIEW_ID); - - int padding = FormattingUtils.dpToPx(context, 8); - listView.setPadding(padding, 0, padding, 0); - listView.setClipToPadding(false); - - innerListContainer.addView(listView, new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - listContainer.addView(innerListContainer, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - root.addView(listContainer, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - - root.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - - return root; - } - - /** - * Define Adapter and Loader on create of Activity - */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - // Give some text to display if there is no data. In a real - // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); -// mSearchView.addTextChangedListener(this); - - setAdapter(new SelectEncryptKeyAdapter(getContext(), null)); setLayoutManager(new LinearLayoutManager(getContext())); - - // Start out with a progress indicator. hideList(false); - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); + GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + LiveData> liveData = viewModel.getGenericLiveData(requireContext(), this::loadSortedUnifiedKeyInfo); + liveData.observe(this, this::onLoadUnifiedKeyData); + } + + @NonNull + private List loadSortedUnifiedKeyInfo() { + List keyInfos = keyRepository.getAllUnifiedKeyInfoWithSecret(); + Collections.sort(keyInfos, sortKeysByPreselectionComparator()); + return keyInfos; + } + + @NonNull + private Comparator sortKeysByPreselectionComparator() { + return (first, second) -> { + if (first == second) { + return 0; + } + boolean firstIsPreselected = selectedMasterKeyIds.contains(first.master_key_id()); + boolean secondIsPreselected = selectedMasterKeyIds.contains(second.master_key_id()); + if (firstIsPreselected != secondIsPreselected) { + return firstIsPreselected ? -1 : 1; + } + String firstUid = first.user_id(); + String secondUid = second.user_id(); + if (firstUid != null && secondUid != null) { + return firstUid.compareTo(secondUid); + } else { + return firstUid == null ? -1 : -1; + } + }; } public long[] getSelectedMasterKeyIds() { - return getAdapter() != null ? - getAdapter().getMasterKeyIds() : new long[0]; + if (keyChoiceAdapter == null) { + return null; + } + // *sigh + Set selectionIds = keyChoiceAdapter.getSelectionIds(); + long[] result = new long[selectionIds.size()]; + int i = 0; + for (Long selectionId : selectionIds) { + result[i++] = selectionId; + } + return result; } - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_EXPIRED, - KeyRings.IS_REVOKED, - KeyRings.HAS_ENCRYPT, - KeyRings.VERIFIED, - KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.CREATION, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT - }; - - String inMasterKeyList = null; - if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { - inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { - if (i != 0) { - inMasterKeyList += ", "; - } - inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]); - } - inMasterKeyList += ")"; - } - - String orderBy = KeyRings.USER_ID + " ASC"; - if (inMasterKeyList != null) { - // sort by selected master keys - orderBy = inMasterKeyList + " DESC, " + orderBy; - } - String where = null; - String whereArgs[] = null; - if (mQuery != null) { - String[] words = mQuery.trim().split("\\s+"); - whereArgs = new String[words.length]; - for (int i = 0; i < words.length; ++i) { - if (where == null) { - where = ""; - } else { - where += " AND "; - } - where += KeyRings.USER_ID + " LIKE ?"; - whereArgs[i] = "%" + words[i] + "%"; - } - } - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, projection, where, whereArgs, orderBy); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - getAdapter().setQuery(mQuery); - getAdapter().swapCursor(SelectEncryptKeyAdapter.PublicKeyCursor.wrap(data)); - - // The list should now be shown. - if (isResumed()) { - showList(true); + public void onLoadUnifiedKeyData(List data) { + if (keyChoiceAdapter == null) { + keyChoiceAdapter = new KeyChoiceAdapter(true, data); + setAdapter(keyChoiceAdapter); + keyChoiceAdapter.setSelectionByIds(selectedMasterKeyIds); } else { - showList(false); + keyChoiceAdapter.setUnifiedKeyInfoItems(data); } - // preselect given master keys - getAdapter().preselectMasterKeyIds(mSelectedMasterKeyIds); - } - - @Override - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - getAdapter().swapCursor(null); - } - - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void afterTextChanged(Editable editable) { - mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; - getLoaderManager().restartLoader(0, null, this); + boolean animateShowList = !isResumed(); + showList(animateShowList); } } From abac3b2445e005696e6118b06864ee914d3c3c3a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 23:02:14 +0200 Subject: [PATCH 057/124] use KeyChoiceAdapter in SelectSignKeyIdListFragment --- .../AppSettingsAllowedKeysListFragment.java | 2 +- .../remote/ui/SelectPublicKeyFragment.java | 2 +- .../ui/SelectSignKeyIdListFragment.java | 160 +++++++----------- .../ui/dialog/RemoteDeduplicatePresenter.java | 2 +- .../keychain/ui/adapter/KeyChoiceAdapter.java | 33 +++- .../keychain/ui/base/RecyclerFragment.java | 13 +- 6 files changed, 106 insertions(+), 106 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index 4206d458f..80682729c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -89,7 +89,7 @@ public class AppSettingsAllowedKeysListFragment extends RecyclerFragment data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = new KeyChoiceAdapter(true, data); + keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data); setAdapter(keyChoiceAdapter); Set checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); keyChoiceAdapter.setSelectionByIds(checkedIds); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index 847321f16..c9726bd40 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -123,7 +123,7 @@ public class SelectPublicKeyFragment extends RecyclerFragment public void onLoadUnifiedKeyData(List data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = new KeyChoiceAdapter(true, data); + keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data); setAdapter(keyChoiceAdapter); keyChoiceAdapter.setSelectionByIds(selectedMasterKeyIds); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 38110e624..f42b2079e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -18,39 +18,45 @@ package org.sufficientlysecure.keychain.remote.ui; +import java.util.List; + import android.app.Activity; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.support.v7.widget.LinearLayoutManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter; +import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; +import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; -public class SelectSignKeyIdListFragment extends RecyclerFragment - implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks { - +public class SelectSignKeyIdListFragment extends RecyclerFragment { private static final String ARG_PACKAGE_NAME = "package_name"; private static final String ARG_PREF_UID = "pref_uid"; public static final String ARG_DATA = "data"; - private Intent mResult; - private String mPrefUid; - private ApiAppDao mApiAppDao; - private String mPackageName; + private ApiAppDao apiAppDao; + private KeyRepository keyRepository; + + private KeyChoiceAdapter keyChoiceAdapter; + + private Intent resultIntent; + private String prefUid; + private String packageName; public static SelectSignKeyIdListFragment newInstance(String packageName, Intent data, String preferredUserId) { SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment(); @@ -68,112 +74,74 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment onCreateKeyDummyClicked()); + + return linearLayout; } - /** - * Define Adapter and Loader on create of Activity - */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mResult = getArguments().getParcelable(ARG_DATA); - mPrefUid = getArguments().getString(ARG_PREF_UID); - mPackageName = getArguments().getString(ARG_PACKAGE_NAME); + resultIntent = getArguments().getParcelable(ARG_DATA); + prefUid = getArguments().getString(ARG_PREF_UID); + packageName = getArguments().getString(ARG_PACKAGE_NAME); - // Give some text to display if there is no data. In a real - // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - - SelectSignKeyAdapter adapter = new SelectSignKeyAdapter(getContext(), null); - adapter.setListener(this); - - setAdapter(adapter); setLayoutManager(new LinearLayoutManager(getContext())); - - // Start out with a progress indicator. hideList(false); - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); + GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); + LiveData> liveData = viewModel.getGenericLiveData( + requireContext(), keyRepository::getAllUnifiedKeyInfoWithSecret); + liveData.observe(this, this::onLoadUnifiedKeyData); } - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_EXPIRED, - KeyRings.IS_REVOKED, - KeyRings.HAS_ENCRYPT, - KeyRings.VERIFIED, - KeyRings.HAS_ANY_SECRET, - KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.CREATION, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT - }; - - String selection = KeyRings.HAS_ANY_SECRET + " != 0"; - - String orderBy = KeyRings.USER_ID + " ASC"; - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, projection, selection, null, orderBy); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - getAdapter().swapCursor(CursorAdapter.KeyCursor.wrap(data)); - - // The list should now be shown. - if (isResumed()) { - showList(true); + public void onLoadUnifiedKeyData(List data) { + if (keyChoiceAdapter == null) { + keyChoiceAdapter = KeyChoiceAdapter.createSingleClickableAdapter(data, this::onSelectKeyItemClicked); + setAdapter(keyChoiceAdapter); } else { - showList(false); + keyChoiceAdapter.setUnifiedKeyInfoItems(data); } + boolean animateShowList = !isResumed(); + showList(animateShowList); } - @Override - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - getAdapter().swapCursor(null); - } - - @Override - public void onDestroy() { - getAdapter().setListener(null); - super.onDestroy(); - } - - @Override public void onCreateKeyDummyClicked() { - OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(mPrefUid); + OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(prefUid); Intent intent = new Intent(getActivity(), CreateKeyActivity.class); intent.putExtra(CreateKeyActivity.EXTRA_NAME, userIdSplit.name); intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userIdSplit.email); - getActivity().startActivityForResult(intent, SelectSignKeyIdActivity.REQUEST_CODE_CREATE_KEY); + + requireActivity().startActivityForResult(intent, SelectSignKeyIdActivity.REQUEST_CODE_CREATE_KEY); } - @Override - public void onSelectKeyItemClicked(long masterKeyId) { - mApiAppDao.addAllowedKeyIdForApp(mPackageName, masterKeyId); - mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); + private void onSelectKeyItemClicked(UnifiedKeyInfo keyInfo) { + apiAppDao.addAllowedKeyIdForApp(packageName, keyInfo.master_key_id()); + resultIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, keyInfo.master_key_id()); - getActivity().setResult(Activity.RESULT_OK, mResult); - getActivity().finish(); + Activity activity = requireActivity(); + activity.setResult(Activity.RESULT_OK, resultIntent); + activity.finish(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index d51a5b0a1..43ec37551 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -62,7 +62,7 @@ class RemoteDeduplicatePresenter { private void onLoadKeyInfos(List data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = new KeyChoiceAdapter(false, data); + keyChoiceAdapter = KeyChoiceAdapter.createSingleChoiceAdapter(data); view.setKeyListAdapter(keyChoiceAdapter); } else { keyChoiceAdapter.setUnifiedKeyInfoItems(data); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index d495cb4e9..107628eab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -4,6 +4,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import android.content.Context; @@ -25,12 +26,27 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceItem public class KeyChoiceAdapter extends FlexibleAdapter { + private final OnKeyClickListener onKeyClickListener; private Integer activeItem; - public KeyChoiceAdapter(boolean isMultiChoice, List items) { + public static KeyChoiceAdapter createSingleClickableAdapter(List items, + OnKeyClickListener onKeyClickListener) { + return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE); + } + + public static KeyChoiceAdapter createSingleChoiceAdapter(List items) { + return new KeyChoiceAdapter(items, null, Mode.SINGLE); + } + + public static KeyChoiceAdapter createMultiChoiceAdapter(List items) { + return new KeyChoiceAdapter(items, null, Mode.MULTI); + } + + private KeyChoiceAdapter(List items, OnKeyClickListener onKeyClickListener, int idle) { super(getKeyChoiceItems(items)); - setMode(isMultiChoice ? Mode.MULTI : Mode.SINGLE); + setMode(idle); addListener((OnItemClickListener) (view, position) -> onClickItem(position)); + this.onKeyClickListener = onKeyClickListener; } @Nullable @@ -50,10 +66,15 @@ public class KeyChoiceAdapter extends FlexibleAdapter { if (getMode() == Mode.MULTI) { toggleSelection(position); notifyItemChanged(position); - } else { + return true; + } else if (getMode() == Mode.SINGLE) { setActiveItem(position); + return true; } - return true; + + KeyChoiceItem item = getItem(position); + onKeyClickListener.onKeyClick(item.keyInfo); + return false; } public void setActiveItem(Integer newActiveItem) { @@ -212,4 +233,8 @@ public class KeyChoiceAdapter extends FlexibleAdapter { } } } + + public interface OnKeyClickListener { + void onKeyClick(UnifiedKeyInfo keyInfo); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/RecyclerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/RecyclerFragment.java index 171db0849..091d073be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/RecyclerFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/RecyclerFragment.java @@ -16,10 +16,10 @@ package org.sufficientlysecure.keychain.ui.base; + import android.content.Context; import android.os.Bundle; import android.os.Handler; -import android.support.annotation.LayoutRes; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.view.Gravity; @@ -32,7 +32,7 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.R; import timber.log.Timber; @@ -124,9 +124,16 @@ public class RecyclerFragment extends Fragment { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - FrameLayout listContainer = new FrameLayout(context); + LinearLayout listContainer = new LinearLayout(context); + listContainer.setOrientation(LinearLayout.VERTICAL); listContainer.setId(INTERNAL_LIST_CONTAINER_ID); + LinearLayout headerLayout = new LinearLayout(context); + headerLayout.setId(R.id.headerlayout); + + listContainer.addView(headerLayout, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + TextView textView = new TextView(context); textView.setId(INTERNAL_EMPTY_VIEW_ID); textView.setGravity(Gravity.CENTER); From 387ec6ed966ddd1bce2b06b2012c526169b2d25b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 23:03:22 +0200 Subject: [PATCH 058/124] ditch unused KeyCursorAdapter classes --- .../ui/adapter/SelectEncryptKeyAdapter.java | 300 ----------- .../ui/adapter/SelectIdentityKeyAdapter.java | 152 ------ .../ui/adapter/SelectSignKeyAdapter.java | 224 --------- .../keychain/ui/adapter/KeyCursorAdapter.java | 59 --- .../ui/util/adapter/CursorAdapter.java | 467 ------------------ 5 files changed, 1202 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectIdentityKeyAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java deleted file mode 100644 index 48c75b29a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; - -import java.util.ArrayList; -import java.util.Arrays; - -public class SelectEncryptKeyAdapter extends KeyCursorAdapter { - - private ArrayList mSelected; - - public SelectEncryptKeyAdapter(Context context, PublicKeyCursor cursor) { - super(context, cursor); - - mSelected = new ArrayList<>(); - } - - private boolean isSelected(int position) { - return mSelected.contains(position); - } - - private void select(int position) { - if (!isSelected(position)) { - mSelected.add(position); - notifyItemChanged(position); - } - } - - private void switchSelected(int position) { - int index = mSelected.indexOf(position); - if (index < 0) { - mSelected.add(position); - } else { - mSelected.remove(index); - } - - notifyItemChanged(position); - } - - public long[] getMasterKeyIds() { - long[] selected = new long[mSelected.size()]; - for (int i = 0; i < selected.length; i++) { - int position = mSelected.get(i); - if (!moveCursor(position)) { - return selected; - } - - selected[i] = getCursor().getKeyId(); - } - - return selected; - } - - public void preselectMasterKeyIds(long[] keyIds) { - if (keyIds != null) { - int count = 0; - for (int i = 0; i < getItemCount(); i++) { - if (!moveCursor(i)) { - continue; - } - - long id = getCursor().getKeyId(); - for (long keyId : keyIds) { - if (id == keyId) { - select(i); - count++; - break; - } - } - - if (count >= keyIds.length) { - return; - } - } - } - } - - @Override - public void onContentChanged() { - mSelected.clear(); - super.onContentChanged(); - } - - @Override - public void onBindViewHolder(EncryptKeyItemHolder holder, PublicKeyCursor cursor, String query) { - holder.bind(cursor, query, isSelected(holder.getAdapterPosition())); - } - - @Override - public EncryptKeyItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new EncryptKeyItemHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.select_encrypt_key_item, parent, false)); - } - - class EncryptKeyItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - private TextView mUserIdText; - private TextView mUserIdRestText; - private TextView mCreationText; - private ImageView mStatusIcon; - private CheckBox mChecked; - - public EncryptKeyItemHolder(View itemView) { - super(itemView); - itemView.setOnClickListener(this); - - mUserIdText = itemView.findViewById(R.id.select_key_item_name); - mUserIdRestText = itemView.findViewById(R.id.select_key_item_email); - mCreationText = itemView.findViewById(R.id.select_key_item_creation); - mStatusIcon = itemView.findViewById(R.id.select_key_item_status_icon); - mChecked = itemView.findViewById(R.id.selected); - } - - public void bind(PublicKeyCursor cursor, String query, boolean selected) { - Highlighter highlighter = new Highlighter(itemView.getContext(), query); - Context context = itemView.getContext(); - - { // set name and stuff, common to both key types - String name = cursor.getName(); - String email = cursor.getEmail(); - if (name != null) { - mUserIdText.setText(highlighter.highlight(name)); - } else { - mUserIdText.setText(R.string.user_id_no_name); - } - if (email != null) { - mUserIdRestText.setText(highlighter.highlight(email)); - mUserIdRestText.setVisibility(View.VISIBLE); - } else { - mUserIdRestText.setVisibility(View.GONE); - } - } - - boolean enabled; - { // set edit button and status, specific by key type. Note: order is important! - int textColor; - if (cursor.isRevoked()) { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - null, - KeyFormattingUtils.State.REVOKED, - R.color.key_flag_gray - ); - - mStatusIcon.setVisibility(View.VISIBLE); - - enabled = false; - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (cursor.isExpired()) { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - null, - KeyFormattingUtils.State.EXPIRED, - R.color.key_flag_gray - ); - - mStatusIcon.setVisibility(View.VISIBLE); - - enabled = false; - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (!cursor.hasEncrypt()) { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - KeyFormattingUtils.State.UNAVAILABLE - ); - - mStatusIcon.setVisibility(View.VISIBLE); - - enabled = false; - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (cursor.isVerified()) { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - KeyFormattingUtils.State.VERIFIED - ); - - mStatusIcon.setVisibility(View.VISIBLE); - - enabled = true; - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } else { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - KeyFormattingUtils.State.UNVERIFIED - ); - - mStatusIcon.setVisibility(View.VISIBLE); - - enabled = true; - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - mUserIdText.setTextColor(textColor); - mUserIdRestText.setTextColor(textColor); - - if (cursor.hasDuplicate()) { - String dateTime = DateUtils.formatDateTime(context, - cursor.getCreationTime(), - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - mCreationText.setText(context.getString(R.string.label_key_created, - dateTime)); - mCreationText.setTextColor(textColor); - mCreationText.setVisibility(View.VISIBLE); - } else { - mCreationText.setVisibility(View.GONE); - } - } - - itemView.setEnabled(enabled); - itemView.setClickable(enabled); - mChecked.setChecked(enabled && selected); - - } - - @Override - public void onClick(View v) { - switchSelected(getAdapterPosition()); - } - } - - public static class PublicKeyCursor extends CursorAdapter.KeyCursor { - public static final String[] PROJECTION; - - static { - ArrayList arr = new ArrayList<>(); - arr.addAll(Arrays.asList(KeyCursor.PROJECTION)); - arr.addAll(Arrays.asList( - KeychainContract.KeyRings.HAS_ENCRYPT, - KeychainContract.KeyRings.VERIFIED - )); - - PROJECTION = arr.toArray(new String[arr.size()]); - } - - public static PublicKeyCursor wrap(Cursor cursor) { - if (cursor != null) { - return new PublicKeyCursor(cursor); - } else { - return null; - } - } - - private PublicKeyCursor(Cursor cursor) { - super(cursor); - } - - public boolean hasEncrypt() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT); - return getInt(index) != 0; - } - - public boolean isVerified() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.VERIFIED); - return getInt(index) != 0; - } - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectIdentityKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectIdentityKeyAdapter.java deleted file mode 100644 index 66e5baee8..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectIdentityKeyAdapter.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2016 Tobias Erthal - * Copyright (C) 2014-2016 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; - - -public class SelectIdentityKeyAdapter extends KeyCursorAdapter { - private SelectSignKeyListener mListener; - - public SelectIdentityKeyAdapter(Context context, Cursor cursor) { - super(context, KeyCursor.wrap(cursor)); - } - - public void setListener(SelectSignKeyListener listener) { - mListener = listener; - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return new SignKeyItemHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.select_identity_key_item, parent, false)); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, KeyCursor cursor, String query) { - ((SignKeyItemHolder) holder).bind(cursor, query); - } - - private class SignKeyItemHolder extends RecyclerView.ViewHolder - implements View.OnClickListener { - - private TextView userIdText; - private TextView creationText; - private ImageView statusIcon; - - SignKeyItemHolder(View itemView) { - super(itemView); - itemView.setClickable(true); - itemView.setOnClickListener(this); - - userIdText = (TextView) itemView.findViewById(R.id.select_key_item_name); - creationText = (TextView) itemView.findViewById(R.id.select_key_item_creation); - statusIcon = (ImageView) itemView.findViewById(R.id.select_key_item_status_icon); - } - - public void bind(KeyCursor cursor, String query) { - Context context = itemView.getContext(); - - { // set name and stuff, common to both key types - String name = cursor.getName(); - if (name != null) { - userIdText.setText(context.getString(R.string.use_key, name)); - } else { - String email = cursor.getEmail(); - userIdText.setText(context.getString(R.string.use_key, email)); - } - } - - { // set edit button and status, specific by key type. Note: order is important! - int textColor; - if (cursor.isRevoked()) { - KeyFormattingUtils.setStatusImage( - context, - statusIcon, - null, - KeyFormattingUtils.State.REVOKED, - R.color.key_flag_gray - ); - - itemView.setEnabled(false); - statusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (cursor.isExpired()) { - KeyFormattingUtils.setStatusImage( - context, - statusIcon, - null, - KeyFormattingUtils.State.EXPIRED, - R.color.key_flag_gray - ); - - itemView.setEnabled(false); - statusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else { - itemView.setEnabled(true); - statusIcon.setImageResource(R.drawable.ic_vpn_key_grey_24dp); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - userIdText.setTextColor(textColor); - - String dateTime = DateUtils.formatDateTime(context, - cursor.getCreationTime(), - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - creationText.setText(context.getString(R.string.label_key_created, - dateTime)); - creationText.setTextColor(textColor); - creationText.setVisibility(View.VISIBLE); - } - - } - - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onSelectKeyItemClicked(getItemId()); - } - } - } - - public interface SelectSignKeyListener { - void onSelectKeyItemClicked(long masterKeyId); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java deleted file mode 100644 index 45617f192..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; - -public class SelectSignKeyAdapter extends KeyCursorAdapter { - private static final int VIEW_TYPE_KEY = 0; - private static final int VIEW_TYPE_DUMMY = 1; - - private SelectSignKeyListener mListener; - - public SelectSignKeyAdapter(Context context, Cursor cursor) { - super(context, KeyCursor.wrap(cursor)); - } - - public void setListener(SelectSignKeyListener listener) { - mListener = listener; - } - - @Override - public int getItemCount() { - return super.getItemCount() + 1; // received items + 1 dummy key - } - - @Override - public long getItemId(int pos) { - if (pos < super.getItemCount()) { - return super.getItemId(pos); - } else { - return RecyclerView.NO_ID; - } - } - - @Override - public int getItemViewType(int position) { - return position == super.getItemCount() ? - VIEW_TYPE_DUMMY : VIEW_TYPE_KEY; - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case VIEW_TYPE_KEY: - return new SignKeyItemHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.select_sign_key_item, parent, false)); - - case VIEW_TYPE_DUMMY: - return new DummyViewHolder(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.select_dummy_key_item, parent, false)); - - default: - return null; - } - - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - if (holder.getItemViewType() == VIEW_TYPE_KEY) { - super.onBindViewHolder(holder, position); - } - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, KeyCursor cursor, String query) { - ((SignKeyItemHolder) holder).bind(cursor, query); - } - - private class DummyViewHolder extends RecyclerView.ViewHolder - implements View.OnClickListener { - - public DummyViewHolder(View itemView) { - super(itemView); - itemView.setClickable(true); - itemView.setOnClickListener(this); - } - - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onCreateKeyDummyClicked(); - } - } - } - - private class SignKeyItemHolder extends RecyclerView.ViewHolder - implements View.OnClickListener { - - private TextView mUserIdText; - private TextView mUserIdRestText; - private TextView mCreationText; - private ImageView mStatusIcon; - - public SignKeyItemHolder(View itemView) { - super(itemView); - itemView.setClickable(true); - itemView.setOnClickListener(this); - - mUserIdText = itemView.findViewById(R.id.select_key_item_name); - mUserIdRestText = itemView.findViewById(R.id.select_key_item_email); - mCreationText = itemView.findViewById(R.id.select_key_item_creation); - mStatusIcon = itemView.findViewById(R.id.select_key_item_status_icon); - } - - public void bind(KeyCursor cursor, String query) { - Highlighter highlighter = new Highlighter(itemView.getContext(), query); - Context context = itemView.getContext(); - - { // set name and stuff, common to both key types - String name = cursor.getName(); - String email = cursor.getEmail(); - if (name != null) { - mUserIdText.setText(highlighter.highlight(name)); - } else { - mUserIdText.setText(R.string.user_id_no_name); - } - if (email != null) { - mUserIdRestText.setText(highlighter.highlight(email)); - mUserIdRestText.setVisibility(View.VISIBLE); - } else { - mUserIdRestText.setVisibility(View.GONE); - } - } - - { // set edit button and status, specific by key type. Note: order is important! - int textColor; - if (cursor.isRevoked()) { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - null, - KeyFormattingUtils.State.REVOKED, - R.color.key_flag_gray - ); - - itemView.setEnabled(false); - mStatusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else if (cursor.isExpired()) { - KeyFormattingUtils.setStatusImage( - context, - mStatusIcon, - null, - KeyFormattingUtils.State.EXPIRED, - R.color.key_flag_gray - ); - - itemView.setEnabled(false); - mStatusIcon.setVisibility(View.VISIBLE); - textColor = ContextCompat.getColor(context, R.color.key_flag_gray); - } else { - itemView.setEnabled(true); - mStatusIcon.setVisibility(View.GONE); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - mUserIdText.setTextColor(textColor); - mUserIdRestText.setTextColor(textColor); - - if (cursor.hasDuplicate()) { - String dateTime = DateUtils.formatDateTime(context, - cursor.getCreationTime(), - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - mCreationText.setText(context.getString(R.string.label_key_created, - dateTime)); - mCreationText.setTextColor(textColor); - mCreationText.setVisibility(View.VISIBLE); - } else { - mCreationText.setVisibility(View.GONE); - } - } - - } - - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onSelectKeyItemClicked(getItemId()); - } - } - } - - public interface SelectSignKeyListener { - void onCreateKeyDummyClicked(); - - void onSelectKeyItemClicked(long masterKeyId); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java deleted file mode 100644 index c46a11740..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; - -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; - -public abstract class KeyCursorAdapter - extends CursorAdapter { - - private String mQuery; - - public KeyCursorAdapter(Context context, C cursor){ - super(context, cursor, 0); - setHasStableIds(true); - } - - public void setQuery(String query) { - mQuery = query; - } - - @Override - public int getItemViewType(int position) { - moveCursorOrThrow(position); - return getItemViewType(getCursor()); - } - - @Override - public long getIdFromCursor(C keyCursor) { - return keyCursor.getKeyId(); - } - - @Override - public void onBindViewHolder(VH holder, int position) { - moveCursorOrThrow(position); - onBindViewHolder(holder, getCursor(), mQuery); - } - - public int getItemViewType(C keyCursor) { return 0; } - public abstract void onBindViewHolder(VH holder, C cursor, String query); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java deleted file mode 100644 index 562f83383..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/CursorAdapter.java +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.util.adapter; - -import android.content.Context; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.CursorWrapper; -import android.database.DataSetObserver; -import android.os.Handler; -import android.support.v7.widget.RecyclerView; - -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter.SimpleCursor; -import timber.log.Timber; - -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; - -public abstract class CursorAdapter - extends RecyclerView.Adapter { - public static final String TAG = "CursorAdapter"; - - private C mCursor; - private Context mContext; - private boolean mDataValid; - - private ChangeObserver mChangeObserver; - private DataSetObserver mDataSetObserver; - - /** - * If set the adapter will register a content observer on the cursor and will call - * {@link #onContentChanged()} when a notification comes in. Be careful when - * using this flag: you will need to unset the current Cursor from the adapter - * to avoid leaks due to its registered observers. This flag is not needed - * when using a CursorAdapter with a - * {@link android.content.CursorLoader}. - */ - public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02; - - /** - * Constructor that allows control over auto-requery. It is recommended - * you not use this, but instead {@link #CursorAdapter(Context, SimpleCursor, int)}. - * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER} - * will always be set. - * - * @param c The cursor from which to get the data. - * @param context The context - */ - public CursorAdapter(Context context, C c) { - setHasStableIds(true); - init(context, c, FLAG_REGISTER_CONTENT_OBSERVER); - } - - /** - * Recommended constructor. - * - * @param c The cursor from which to get the data. - * @param context The context - * @param flags Flags used to determine the behavior of the adapter - * @see #FLAG_REGISTER_CONTENT_OBSERVER - */ - public CursorAdapter(Context context, C c, int flags) { - setHasStableIds(true); - init(context, c, flags); - } - - private void init(Context context, C c, int flags) { - boolean cursorPresent = c != null; - mCursor = c; - mDataValid = cursorPresent; - mContext = context; - if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) { - mChangeObserver = new ChangeObserver(); - mDataSetObserver = new MyDataSetObserver(); - } else { - mChangeObserver = null; - mDataSetObserver = null; - } - - if (cursorPresent) { - if (mChangeObserver != null) c.registerContentObserver(mChangeObserver); - if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver); - } - - setHasStableIds(true); - } - - /** - * Returns the cursor. - * - * @return the cursor. - */ - public C getCursor() { - return mCursor; - } - - public Context getContext() { - return mContext; - } - - /** - * @see android.support.v7.widget.RecyclerView.Adapter#getItemCount() - */ - @Override - public int getItemCount() { - if (mDataValid && mCursor != null) { - return mCursor.getCount(); - } else { - return 0; - } - } - - public boolean hasValidData() { - mDataValid = hasOpenCursor(); - return mDataValid; - } - - private boolean hasOpenCursor() { - Cursor cursor = getCursor(); - if (cursor != null && cursor.isClosed()) { - swapCursor(null); - return false; - } - - return cursor != null; - } - - /** - * @param position Adapter position to query - * @return the id of the item - * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int) - */ - @Override - public long getItemId(int position) { - if (mDataValid && mCursor != null) { - if (moveCursor(position)) { - return getIdFromCursor(mCursor); - } else { - return RecyclerView.NO_ID; - } - } else { - return RecyclerView.NO_ID; - } - } - - /** - * Return the id of the item represented by the row the cursor - * is currently moved to. - * - * @param cursor The cursor moved to the correct position. - * @return The id of the dataset - */ - public long getIdFromCursor(C cursor) { - if (cursor != null) { - return cursor.getEntryId(); - } else { - return RecyclerView.NO_ID; - } - } - - public void moveCursorOrThrow(int position) - throws IndexOutOfBoundsException, IllegalStateException { - - if (position >= getItemCount() || position < -1) { - throw new IndexOutOfBoundsException("Position: " + position - + " is invalid for this data set!"); - } - - if (!mDataValid) { - throw new IllegalStateException("Attempt to move cursor over invalid data set!"); - } - - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("Couldn't move cursor from position: " - + mCursor.getPosition() + " to position: " + position + "!"); - } - } - - public boolean moveCursor(int position) { - if (position >= getItemCount() || position < -1) { - Timber.w("Position: %d is invalid for this data set!"); - return false; - } - - if (!mDataValid) { - Timber.d("Attempt to move cursor over invalid data set!"); - } - - return mCursor.moveToPosition(position); - } - - /** - * Change the underlying cursor to a new cursor. If there is an existing cursor it will be - * closed. - * - * @param cursor The new cursor to be used - */ - public void changeCursor(C cursor) { - Cursor old = swapCursor(cursor); - if (old != null) { - old.close(); - } - } - - /** - * Swap in a new Cursor, returning the old Cursor. Unlike - * {@link #changeCursor(SimpleCursor)}, the returned old Cursor is not - * closed. - * - * @param newCursor The new cursor to be used. - * @return Returns the previously set Cursor, or null if there wasa not one. - * If the given new Cursor is the same instance is the previously set - * Cursor, null is also returned. - */ - public C swapCursor(C newCursor) { - if (newCursor == mCursor) { - return null; - } - - C oldCursor = mCursor; - if (oldCursor != null) { - if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); - if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); - } - - mCursor = newCursor; - if (newCursor != null) { - if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); - if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); - mDataValid = true; - // notify the observers about the new cursor - onContentChanged(); - } else { - mDataValid = false; - // notify the observers about the lack of a data set - onContentChanged(); - } - - return oldCursor; - } - - /** - *

Converts the cursor into a CharSequence. Subclasses should override this - * method to convert their results. The default implementation returns an - * empty String for null values or the default String representation of - * the value.

- * - * @param cursor the cursor to convert to a CharSequence - * @return a CharSequence representing the value - */ - public CharSequence convertToString(Cursor cursor) { - return cursor == null ? "" : cursor.toString(); - } - - /** - * Called when the {@link ContentObserver} on the cursor receives a change notification. - * The default implementation provides the auto-requery logic, but may be overridden by - * sub classes. - * - * @see ContentObserver#onChange(boolean) - */ - protected void onContentChanged() { - notifyDataSetChanged(); - } - - private class ChangeObserver extends ContentObserver { - public ChangeObserver() { - super(new Handler()); - } - - @Override - public boolean deliverSelfNotifications() { - return true; - } - - @Override - public void onChange(boolean selfChange) { - onContentChanged(); - } - } - - private class MyDataSetObserver extends DataSetObserver { - @Override - public void onChanged() { - mDataValid = true; - onContentChanged(); - } - - @Override - public void onInvalidated() { - mDataValid = false; - onContentChanged(); - } - } - - public static class SimpleCursor extends CursorWrapper { - public static final String[] PROJECTION = {"_id"}; - - public static T wrap(Cursor cursor, Class type) { - if (cursor != null) { - try { - Constructor constructor = type.getConstructor(Cursor.class); - return constructor.newInstance(cursor); - } catch (Exception e) { - Timber.e(e, "Could not create instance of cursor wrapper!"); - } - } - - return null; - } - - private HashMap mColumnIndices; - - /** - * Creates a cursor wrapper. - * - * @param cursor The underlying cursor to wrap. - */ - public SimpleCursor(Cursor cursor) { - super(cursor); - mColumnIndices = new HashMap<>(cursor.getColumnCount() * 4 / 3, 0.75f); - } - - @Override - public void close() { - mColumnIndices.clear(); - super.close(); - } - - public final int getEntryId() { - int index = getColumnIndexOrThrow("_id"); - return getInt(index); - } - - @Override - public final int getColumnIndexOrThrow(String colName) { - Integer colIndex = mColumnIndices.get(colName); - if (colIndex == null) { - colIndex = super.getColumnIndexOrThrow(colName); - mColumnIndices.put(colName, colIndex); - } else if (colIndex < 0) { - throw new IllegalArgumentException("Could not get column index for name: \"" + colName + "\""); - } - - return colIndex; - } - - @Override - public final int getColumnIndex(String colName) { - Integer colIndex = mColumnIndices.get(colName); - if (colIndex == null) { - colIndex = super.getColumnIndex(colName); - mColumnIndices.put(colName, colIndex); - } - - return colIndex; - } - } - - public static class KeyCursor extends SimpleCursor { - public static final String[] PROJECTION; - - static { - ArrayList arr = new ArrayList<>(); - arr.addAll(Arrays.asList(SimpleCursor.PROJECTION)); - arr.addAll(Arrays.asList( - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.IS_SECURE, - KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID, - KeychainContract.KeyRings.CREATION, - KeychainContract.KeyRings.NAME, - KeychainContract.KeyRings.EMAIL, - KeychainContract.KeyRings.COMMENT - )); - - PROJECTION = arr.toArray(new String[arr.size()]); - } - - public static KeyCursor wrap(Cursor cursor) { - if (cursor != null) { - return new KeyCursor(cursor); - } else { - return null; - } - } - - /** - * Creates a cursor wrapper. - * - * @param cursor The underlying cursor to wrap. - */ - protected KeyCursor(Cursor cursor) { - super(cursor); - } - - public long getKeyId() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.MASTER_KEY_ID); - return getLong(index); - } - - public String getName() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.NAME); - return getString(index); - } - - public String getEmail() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.EMAIL); - return getString(index); - } - - public String getComment() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.COMMENT); - return getString(index); - } - - public boolean hasDuplicate() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID); - return getLong(index) > 0L; - } - - public boolean isRevoked() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.IS_REVOKED); - return getInt(index) > 0; - } - - public boolean isExpired() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.IS_EXPIRED); - return getInt(index) > 0; - } - - public boolean isSecure() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.IS_SECURE); - return getInt(index) > 0; - } - - public long getCreationTime() { - int index = getColumnIndexOrThrow(KeychainContract.KeyRings.CREATION); - return getLong(index) * 1000; - } - - public Date getCreationDate() { - return new Date(getCreationTime()); - } - } -} \ No newline at end of file From febe9cbe922b7299aa3577978971e282dd0bf89f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 27 Jun 2018 23:51:16 +0200 Subject: [PATCH 059/124] use MaterialChipsInput for encryption recipients --- OpenKeychain/build.gradle | 3 +- .../keychain/matcher/CustomMatchers.java | 1 - .../ui/EncryptModeAsymmetricFragment.java | 94 ++++++--- .../ui/widget/EncryptKeyCompletionView.java | 179 ------------------ .../layout/encrypt_asymmetric_fragment.xml | 46 +++-- build.gradle | 1 + 6 files changed, 97 insertions(+), 227 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 4e9bb106d..ae630da4f 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -28,7 +28,7 @@ dependencies { // UI compile 'org.sufficientlysecure:html-textview:3.1' - compile 'com.splitwise:tokenautocomplete:2.0.8@aar' + compile 'com.github.sikeeoh:MaterialChipsInput:1.1.1' compile 'com.jpardogo.materialtabstrip:library:1.1.1' compile 'com.getbase:floatingactionbutton:1.10.1' compile 'com.nispok:snackbar:2.11.0' @@ -150,7 +150,6 @@ dependencyVerification { 'com.squareup.okhttp3:okhttp:a0d01017a42bba26e507fc6d448bb36e536f4b6e612f7c42de30bbdac2b7785e', 'org.apache.james:apache-mime4j-dom:e18717fe6d36f32e5c5f7cbeea1a9bf04645fdabc84e7e8374d9da10fd52e78d', 'org.apache.james:apache-mime4j-core:561987f604911e1870b2b4eabf0b0658d666c66cb1e65fba3e9e4bffe63acab9', - 'com.splitwise:tokenautocomplete:f921f83ee26b5265f719b312c30452ef8e219557826c5ce5bf02e29647967939', 'com.cocosw:bottomsheet:85bd91fd837b02ebd7a888501cb26035c7cd985a6aa87303fca249da8231a2c3', 'eu.davidea:flexible-adapter-livedata:c8718b46ff4fbf290ea18f0c5bfe8326badeadf5fd95899a1404c561a24f48a1', 'com.mikepenz:materialdrawer:8bba1428dcef5ad7c2decf49c612ad980b38e2f1031cbd66c152a8a104793929', diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java index aef83afb0..d029c5974 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java @@ -32,7 +32,6 @@ import org.hamcrest.Description; import org.hamcrest.Matcher; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; -import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 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 6e4c15675..21aadc67d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -23,7 +23,9 @@ import java.util.Iterator; import java.util.List; import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; import android.arch.lifecycle.ViewModelProviders; +import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.view.LayoutInflater; @@ -31,30 +33,30 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ViewAnimator; -import com.tokenautocomplete.TokenCompleteTextView.TokenListener; +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.ChipsInput.ChipsListener; +import com.pchmn.materialchips.model.Chip; +import com.pchmn.materialchips.model.ChipInterface; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; -import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Passphrase; import timber.log.Timber; public class EncryptModeAsymmetricFragment extends EncryptModeFragment { - KeyRepository mKeyRepository; private KeySpinner mSignKeySpinner; - private EncryptKeyCompletionView mEncryptKeyView; + private ChipsInput mEncryptKeyView; public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; @@ -80,7 +82,9 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner = view.findViewById(R.id.sign_key_spinner); mEncryptKeyView = view.findViewById(R.id.recipient_list); - mEncryptKeyView.setThreshold(1); // Start working from first character + + ViewGroup filterableListAnchor = view.findViewById(R.id.anchor_dropdown_encrypt); + mEncryptKeyView.setFilterableListLayout(filterableListAnchor); final ViewAnimator vSignatureIcon = view.findViewById(R.id.result_signature_icon); mSignKeySpinner.setOnKeyChangedListener(masterKeyId -> { @@ -92,21 +96,31 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner.setShowNone(R.string.cert_none); final ViewAnimator vEncryptionIcon = view.findViewById(R.id.result_encryption_icon); - mEncryptKeyView.setTokenListener(new TokenListener() { + mEncryptKeyView.addChipsListener(new ChipsListener() { @Override - public void onTokenAdded(KeyItem o) { + public void onChipAdded(ChipInterface chipInterface, int newSize) { if (vEncryptionIcon.getDisplayedChild() != 1) { vEncryptionIcon.setDisplayedChild(1); } } @Override - public void onTokenRemoved(KeyItem o) { - int child = mEncryptKeyView.getObjects().isEmpty() ? 0 : 1; + public void onChipRemoved(ChipInterface chipInterface, int newSize) { + int child = newSize == 0 ? 0 : 1; if (vEncryptionIcon.getDisplayedChild() != child) { vEncryptionIcon.setDisplayedChild(child); } } + + @Override + public void onTextChanged(CharSequence charSequence) { + + } + + @Override + public void onActionDone(CharSequence charSequence) { + + } }); return view; @@ -115,12 +129,10 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mKeyRepository = KeyRepository.create(requireContext()); - GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); - LiveData> liveData = viewModel.getGenericLiveData(requireContext(), - mKeyRepository::getAllUnifiedKeyInfoWithSecret); - liveData.observe(this, mSignKeySpinner::setData); + EncryptModeViewModel viewModel = ViewModelProviders.of(this).get(EncryptModeViewModel.class); + viewModel.getSignKeyLiveData(requireContext()).observe(this, mSignKeySpinner::setData); + viewModel.getEncryptRecipientLiveData(requireContext()).observe(this, this::onLoadEncryptRecipients); // preselect keys given, from state or arguments if (savedInstanceState == null) { @@ -131,7 +143,40 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); preselectKeys(signatureKeyId, encryptionKeyIds); } + } + private void onLoadEncryptRecipients(List keyInfoChips) { + mEncryptKeyView.setFilterableList(keyInfoChips); + } + + public static class EncryptModeViewModel extends ViewModel { + private LiveData> signKeyLiveData; + private LiveData> encryptRecipientLiveData; + + LiveData> getSignKeyLiveData(Context context) { + if (signKeyLiveData == null) { + signKeyLiveData = new GenericLiveData<>(context, null, () -> { + KeyRepository keyRepository = KeyRepository.create(context); + return keyRepository.getAllUnifiedKeyInfoWithSecret(); + }); + } + return signKeyLiveData; + } + + LiveData> getEncryptRecipientLiveData(Context context) { + if (encryptRecipientLiveData == null) { + encryptRecipientLiveData = new GenericLiveData<>(context, null, () -> { + KeyRepository keyRepository = KeyRepository.create(context); + List keyInfos = keyRepository.getAllUnifiedKeyInfo(); + ArrayList result = new ArrayList<>(); + for (UnifiedKeyInfo keyInfo : keyInfos) { + result.add(new Chip(keyInfo.master_key_id(), keyInfo.name(), keyInfo.email())); + } + return result; + }); + } + return encryptRecipientLiveData; + } } /** @@ -153,7 +198,8 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { try { CanonicalizedPublicKeyRing ring = mKeyRepository.getCanonicalizedPublicKeyRing(preselectedId); - mEncryptKeyView.addObject(new KeyItem(ring)); + Chip infooo = new Chip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo"); + mEncryptKeyView.addChip(infooo); } catch (NotFoundException e) { Timber.e(e, "key not found for encryption!"); Notify.create(getActivity(), getString(R.string.error_preselect_encrypt_key, @@ -180,10 +226,8 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public long[] getAsymmetricEncryptionKeyIds() { List keyIds = new ArrayList<>(); - for (Object object : mEncryptKeyView.getObjects()) { - if (object instanceof KeyItem) { - keyIds.add(((KeyItem) object).mKeyId); - } + for (ChipInterface chip : mEncryptKeyView.getSelectedChipList()) { + keyIds.add((long) chip.getId()); } long[] keyIdsArr = new long[keyIds.size()]; @@ -197,16 +241,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public String[] getAsymmetricEncryptionUserIds() { - List userIds = new ArrayList<>(); - for (Object object : mEncryptKeyView.getObjects()) { - if (object instanceof KeyItem) { - userIds.add(((KeyItem) object).mUserIdFull); - } + for (ChipInterface chip : mEncryptKeyView.getSelectedChipList()) { + userIds.add(chip.getInfo()); } return userIds.toArray(new String[userIds.size()]); - } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java deleted file mode 100644 index 95b68a9f6..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.widget; - - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Color; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.TextView; - -import com.tokenautocomplete.TokenCompleteTextView; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; -import timber.log.Timber; - - -public class EncryptKeyCompletionView extends TokenCompleteTextView - implements LoaderCallbacks { - - public static final String ARG_QUERY = "query"; - - private KeyAdapter mAdapter; - private LoaderManager mLoaderManager; - - public EncryptKeyCompletionView(Context context) { - super(context); - initView(); - } - - public EncryptKeyCompletionView(Context context, AttributeSet attrs) { - super(context, attrs); - initView(); - } - - public EncryptKeyCompletionView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initView(); - } - - private void initView() { - allowDuplicates(false); - - mAdapter = new KeyAdapter(getContext(), null, 0); - setAdapter(mAdapter); - } - - @Override - protected View getViewForObject(KeyItem keyItem) { - LayoutInflater l = LayoutInflater.from(getContext()); - View view = l.inflate(R.layout.recipient_box_entry, null); - ((TextView) view.findViewById(android.R.id.text1)).setText(keyItem.getReadableName()); - - if (keyItem.mIsRevoked || !keyItem.mHasEncrypt || keyItem.mIsExpired) { - ((TextView) view.findViewById(android.R.id.text1)).setTextColor(Color.RED); - } - - return view; - } - - @Override - protected KeyItem defaultObject(String completionText) { - return null; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (getContext() instanceof FragmentActivity) { - mLoaderManager = ((FragmentActivity) getContext()).getSupportLoaderManager(); - } else { - Timber.e("EncryptKeyCompletionView must be attached to a FragmentActivity, this is " + - getContext().getClass()); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mLoaderManager = null; - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - // These are the rows that we will retrieve. - Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - - String[] projection = KeyAdapter.getProjectionWith(new String[]{ - KeychainContract.KeyRings.HAS_ENCRYPT, - }); - - String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " - + KeyRings.IS_EXPIRED + " = 0 AND " - + Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0"; - - String query = args.getString(ARG_QUERY); - mAdapter.setSearchQuery(query); - - where += " AND " + KeyRings.USER_ID + " LIKE ?"; - - return new CursorLoader(getContext(), baseUri, projection, where, - new String[]{"%" + query + "%"}, null); - - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - mAdapter.swapCursor(data); - } - - @Override - public void onLoaderReset(Loader loader) { - mAdapter.swapCursor(null); - } - - @Override - public void showDropDown() { - if (mAdapter == null || mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) { - return; - } - super.showDropDown(); - } - - @Override - public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { - super.onFocusChanged(hasFocus, direction, previous); - if (hasFocus) { - ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)) - .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); - } - } - - @Override - protected void performFiltering(@NonNull CharSequence text, int start, int end, int keyCode) { -// super.performFiltering(text, start, end, keyCode); - String query = text.subSequence(start, end).toString(); - if (TextUtils.isEmpty(query) || query.length() < 2) { - mAdapter.swapCursor(null); - return; - } - Bundle args = new Bundle(); - args.putString(ARG_QUERY, query); - mLoaderManager.restartLoader(0, args, this); - } - -} diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index cab667f63..b191c1b80 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -1,18 +1,19 @@ + > + android:layout_marginRight="16dp" + android:layout_marginLeft="16dp" + android:background="?android:attr/editTextBackground"> + android:outAnimation="@anim/fade_out" + > - + app:hint="@string/label_to" + app:chip_hasAvatarIcon="false" + app:maxRows="2" + app:chip_detailed_backgroundColor="@color/colorChipViewBackground" + /> + + + android:background="?android:attr/editTextBackground"> + android:layout_height="wrap_content"> + android:paddingLeft="8dp" + android:paddingRight="8dp" + /> Date: Wed, 27 Jun 2018 23:51:25 +0200 Subject: [PATCH 060/124] remove unified query from KeychainProvider --- .../keychain/actions/CustomActions.java | 1 - .../keychain/matcher/CustomMatchers.java | 1 - .../keychain/operations/BackupOperation.java | 7 - .../keychain/provider/KeychainContract.java | 20 -- .../keychain/provider/KeychainProvider.java | 270 +------------- .../keychain/ui/adapter/KeyAdapter.java | 332 ------------------ .../ui/adapter/KeySelectableAdapter.java | 98 ------ 7 files changed, 11 insertions(+), 718 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java index 14fb14e35..032aae0ad 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java @@ -28,7 +28,6 @@ import com.tokenautocomplete.TokenCompleteTextView; import org.hamcrest.Matcher; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import static android.support.test.InstrumentationRegistry.getTargetContext; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java index d029c5974..fd44c4ad1 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/matcher/CustomMatchers.java @@ -31,7 +31,6 @@ import com.nispok.snackbar.Snackbar; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 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 65bbd4d7c..1f5f59a1a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java @@ -52,7 +52,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; @@ -75,12 +74,6 @@ import timber.log.Timber; * either the name of a file or an output uri to write to. */ public class BackupOperation extends BaseOperation { - - private static final String[] PROJECTION = new String[] { - KeyRings.MASTER_KEY_ID, - KeyRings.HAS_ANY_SECRET - }; - // this is a very simple matcher, we only need basic sanitization private static final Pattern HEADER_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+: [^\\n]+"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 76ee5f6f8..e991cc99e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -97,34 +97,14 @@ public class KeychainContract { public static final String BASE_KEY_SIGNATURES = "key_signatures"; - public static final String PATH_UNIFIED = "unified"; - public static final String PATH_PUBLIC = "public"; public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_KEYS = "keys"; public static final String PATH_CERTS = "certs"; public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns { - public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; - public static final String IS_REVOKED = KeysColumns.IS_REVOKED; - public static final String IS_SECURE = KeysColumns.IS_SECURE; - public static final String VERIFIED = CertsColumns.VERIFIED; - public static final String IS_EXPIRED = "is_expired"; - public static final String HAS_ANY_SECRET = "has_any_secret"; - public static final String HAS_ENCRYPT = "has_encrypt"; - public static final String HAS_SIGN_SECRET = "has_sign_secret"; - public static final String HAS_CERTIFY_SECRET = "has_certify_secret"; - public static final String HAS_AUTHENTICATE = "has_authenticate"; - public static final String HAS_AUTHENTICATE_SECRET = "has_authenticate_secret"; - public static final String HAS_DUPLICATE_USER_ID = "has_duplicate_user_id"; - public static final String API_KNOWN_TO_PACKAGE_NAMES = "known_to_apps"; - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); - - public static Uri buildUnifiedKeyRingsUri() { - return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); - } } public static class KeyRingData implements KeyRingsColumns, BaseColumns { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index a3d63d849..ec13f24ca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -18,11 +18,6 @@ package org.sufficientlysecure.keychain.provider; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; - import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentValues; @@ -30,32 +25,21 @@ import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.model.AutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.util.DatabaseUtil; import timber.log.Timber; -import static android.database.DatabaseUtils.dumpCursorToString; - public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface { - - private static final int KEY_RINGS_UNIFIED = 101; - - private static final int KEY_RING_UNIFIED = 200; private static final int KEY_RING_KEYS = 201; private static final int KEY_RING_USER_IDS = 202; private static final int KEY_RING_PUBLIC = 203; @@ -74,33 +58,16 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe String authority = KeychainContract.CONTENT_AUTHORITY; - /* - * list key_rings - * - *
-         * key_rings/unified
-         * key_rings/user_ids
-         * 
- */ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS - + "/" + KeychainContract.PATH_UNIFIED, - KEY_RINGS_UNIFIED); - /* * list key_ring specifics * *
-         * key_rings/_/unified
          * key_rings/_/keys
          * key_rings/_/user_ids
-         * key_rings/_/linked_ids
          * key_rings/_/public
          * key_rings/_/certs
          * 
*/ - matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" - + KeychainContract.PATH_UNIFIED, - KEY_RING_UNIFIED); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_KEYS, KEY_RING_KEYS); @@ -122,9 +89,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private KeychainDatabase mKeychainDatabase; - /** - * {@inheritDoc} - */ @Override public boolean onCreate() { mUriMatcher = buildUriMatcher(); @@ -132,234 +96,28 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } public KeychainDatabase getDb() { - if(mKeychainDatabase == null) + if(mKeychainDatabase == null) { mKeychainDatabase = KeychainDatabase.getInstance(getContext()); + } return mKeychainDatabase; } - /** - * {@inheritDoc} - */ @Override public String getType(@NonNull Uri uri) { throw new UnsupportedOperationException(); } + @Override + public Cursor query( + @NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + throw new UnsupportedOperationException(); + } + /** * {@inheritDoc} */ @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - Timber.v("query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); - - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - int match = mUriMatcher.match(uri); - - // all query() parameters, for good measure - String groupBy; - - switch (match) { - case KEY_RING_UNIFIED: - case KEY_RINGS_UNIFIED: { - HashMap projectionMap = new HashMap<>(); - projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); - projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); - projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID); - projectionMap.put(KeyRings.KEY_SIZE, Tables.KEYS + "." + Keys.KEY_SIZE); - projectionMap.put(KeyRings.KEY_CURVE_OID, Tables.KEYS + "." + Keys.KEY_CURVE_OID); - projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED); - projectionMap.put(KeyRings.IS_SECURE, Tables.KEYS + "." + Keys.IS_SECURE); - projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY); - projectionMap.put(KeyRings.CAN_ENCRYPT, Tables.KEYS + "." + Keys.CAN_ENCRYPT); - projectionMap.put(KeyRings.CAN_SIGN, Tables.KEYS + "." + Keys.CAN_SIGN); - projectionMap.put(KeyRings.CAN_AUTHENTICATE, Tables.KEYS + "." + Keys.CAN_AUTHENTICATE); - projectionMap.put(KeyRings.CREATION, Tables.KEYS + "." + Keys.CREATION); - projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY); - projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM); - projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); - projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID); - projectionMap.put(KeyRings.NAME, Tables.USER_PACKETS + "." + UserPackets.NAME); - projectionMap.put(KeyRings.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL); - projectionMap.put(KeyRings.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT); - projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID, - "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups" - + " WHERE dups." + UserPackets.MASTER_KEY_ID - + " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND dups." + UserPackets.RANK + " = 0" - + " AND dups." + UserPackets.NAME - + " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE" - + " AND dups." + UserPackets.EMAIL - + " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE" - + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID); - projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); - projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET); - projectionMap.put(KeyRings.HAS_ANY_SECRET, - "(EXISTS (SELECT * FROM " + Tables.KEYS + " AS k WHERE " - + "k." + Keys.HAS_SECRET + " != 0" - + " AND k." + Keys.MASTER_KEY_ID + " = " - + Tables.KEYS + "." + KeyRingData.MASTER_KEY_ID - + ")) AS " + KeyRings.HAS_ANY_SECRET); - projectionMap.put(KeyRings.HAS_ENCRYPT, - "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); - projectionMap.put(KeyRings.HAS_SIGN_SECRET, - "kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN_SECRET); - projectionMap.put(KeyRings.HAS_AUTHENTICATE, - "kA." + Keys.KEY_ID + " AS " + KeyRings.HAS_AUTHENTICATE); - projectionMap.put(KeyRings.HAS_AUTHENTICATE_SECRET, - "kA." + Keys.KEY_ID + " AS " + KeyRings.HAS_AUTHENTICATE_SECRET); - projectionMap.put(KeyRings.HAS_CERTIFY_SECRET, - "kC." + Keys.KEY_ID + " AS " + KeyRings.HAS_CERTIFY_SECRET); - projectionMap.put(KeyRings.IS_EXPIRED, - "(" + Tables.KEYS + "." + Keys.EXPIRY + " IS NOT NULL AND " + Tables.KEYS + "." + Keys.EXPIRY - + " < " + new Date().getTime() / 1000 + ") AS " + KeyRings.IS_EXPIRED); - projectionMap.put(KeyRings.API_KNOWN_TO_PACKAGE_NAMES, - "GROUP_CONCAT(DISTINCT aTI." + AutocryptPeer.PACKAGE_NAME + ") AS " - + KeyRings.API_KNOWN_TO_PACKAGE_NAMES); - qb.setProjectionMap(projectionMap); - - if (projection == null) { - throw new IllegalArgumentException("Please provide a projection!"); - } - - // Need this as list so we can search in it - List plist = Arrays.asList(projection); - - qb.setTables( - Tables.KEYS - + " INNER JOIN " + Tables.USER_PACKETS + " ON (" - + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " = " - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID - // we KNOW that the rank zero user packet is a user id! - + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = 0" - + ") LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " = " - + Tables.CERTS + "." + KeyRings.MASTER_KEY_ID - + " AND " + Tables.CERTS + "." + Certs.VERIFIED - + " = " + Certs.VERIFIED_SECRET - + ")" - // fairly expensive joins following, only do when requested - + (plist.contains(KeyRings.HAS_ENCRYPT) ? - " LEFT JOIN " + Tables.KEYS + " AS kE ON (" - +"kE." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND kE." + Keys.IS_REVOKED + " = 0" - + " AND kE." + Keys.IS_SECURE + " = 1" - + " AND kE." + Keys.CAN_ENCRYPT + " = 1" - + " AND ( kE." + Keys.EXPIRY + " IS NULL OR kE." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")" : "") - + (plist.contains(KeyRings.HAS_SIGN_SECRET) ? - " LEFT JOIN " + Tables.KEYS + " AS kS ON (" - +"kS." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND kS." + Keys.IS_REVOKED + " = 0" - + " AND kS." + Keys.IS_SECURE + " = 1" - + " AND kS." + Keys.CAN_SIGN + " = 1" - + " AND kS." + Keys.HAS_SECRET + " > 1" - + " AND ( kS." + Keys.EXPIRY + " IS NULL OR kS." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")" : "") - + (plist.contains(KeyRings.HAS_AUTHENTICATE) ? - " LEFT JOIN " + Tables.KEYS + " AS kA ON (" - +"kA." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND kA." + Keys.IS_REVOKED + " = 0" - + " AND kA." + Keys.IS_SECURE + " = 1" - + " AND kA." + Keys.CAN_AUTHENTICATE + " = 1" - + " AND ( kA." + Keys.EXPIRY + " IS NULL OR kA." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")" : "") - + (plist.contains(KeyRings.HAS_AUTHENTICATE_SECRET) ? - " LEFT JOIN " + Tables.KEYS + " AS kA ON (" - +"kA." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND kA." + Keys.IS_REVOKED + " = 0" - + " AND kA." + Keys.IS_SECURE + " = 1" - + " AND kA." + Keys.CAN_AUTHENTICATE + " = 1" - + " AND kA." + Keys.HAS_SECRET + " > 1" - + " AND ( kA." + Keys.EXPIRY + " IS NULL OR kA." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")" : "") - + (plist.contains(KeyRings.HAS_CERTIFY_SECRET) ? - " LEFT JOIN " + Tables.KEYS + " AS kC ON (" - +"kC." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND kC." + Keys.IS_REVOKED + " = 0" - + " AND kC." + Keys.IS_SECURE + " = 1" - + " AND kC." + Keys.CAN_CERTIFY + " = 1" - + " AND kC." + Keys.HAS_SECRET + " > 1" - + " AND ( kC." + Keys.EXPIRY + " IS NULL OR kC." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")" : "") - + (plist.contains(KeyRings.API_KNOWN_TO_PACKAGE_NAMES) ? - " LEFT JOIN " + AutocryptPeer.TABLE_NAME + " AS aTI ON (" - +"aTI." + AutocryptPeer.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + ")" : "") - ); - qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0"); - // in case there are multiple verifying certificates - groupBy = Tables.KEYS + "." + Keys.MASTER_KEY_ID; - - qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"; - } - - // uri to watch is all /key_rings/ - uri = KeyRings.CONTENT_URI; - - break; - } - - default: { - throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); - } - - } - - // If no sort order is specified use the default - String orderBy; - if (TextUtils.isEmpty(sortOrder)) { - orderBy = null; - } else { - orderBy = sortOrder; - } - - SupportSQLiteDatabase db = getDb().getReadableDatabase(); - - String query = qb.buildQuery(projection, selection, groupBy, null, orderBy, null); - Cursor cursor = db.query(query, selectionArgs); - if (cursor != null) { - // Tell the cursor what uri to watch, so it knows when its source data changes - cursor.setNotificationUri(getContext().getContentResolver(), uri); - } - - Timber.d("Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null)); - - if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) { - Timber.d("Cursor: " + dumpCursorToString(cursor)); - } - - if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) { - String rawQuery = qb.buildQuery(projection, selection, groupBy, null, orderBy, null); - DatabaseUtil.explainQuery(db, rawQuery); - } - - return cursor; - } - - /** - * {@inheritDoc} - */ - @Override - public Uri insert(Uri uri, ContentValues values) { + public Uri insert(@NonNull Uri uri, ContentValues values) { Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")"); final SupportSQLiteDatabase db = getDb().getWritableDatabase(); @@ -372,7 +130,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe switch (match) { case KEY_RING_PUBLIC: { db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values); - keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); + keyId = values.getAsLong(Keys.MASTER_KEY_ID); break; } case KEY_RING_KEYS: { @@ -424,19 +182,13 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe return rowUri; } - /** - * {@inheritDoc} - */ @Override public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { throw new UnsupportedOperationException(); } - /** - * {@inheritDoc} - */ @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")"); final SupportSQLiteDatabase db = getDb().getWritableDatabase(); 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 deleted file mode 100644 index 9912cb697..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.widget.CursorAdapter; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.openintents.openpgp.util.OpenPgpUtils; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; - -public class KeyAdapter extends CursorAdapter { - - protected String mQuery; - protected LayoutInflater mInflater; - protected Context mContext; - - // These are the rows that we will retrieve. - public static final String[] PROJECTION = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_REVOKED, - KeyRings.IS_EXPIRED, - KeyRings.IS_SECURE, - KeyRings.VERIFIED, - KeyRings.HAS_ANY_SECRET, - KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.FINGERPRINT, - KeyRings.CREATION, - KeyRings.HAS_ENCRYPT, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT - }; - - public static final int INDEX_MASTER_KEY_ID = 1; - public static final int INDEX_USER_ID = 2; - public static final int INDEX_IS_REVOKED = 3; - public static final int INDEX_IS_EXPIRED = 4; - public static final int INDEX_IS_SECURE = 5; - public static final int INDEX_VERIFIED = 6; - public static final int INDEX_HAS_ANY_SECRET = 7; - public static final int INDEX_HAS_DUPLICATE_USER_ID = 8; - public static final int INDEX_FINGERPRINT = 9; - public static final int INDEX_CREATION = 10; - public static final int INDEX_HAS_ENCRYPT = 11; - public static final int INDEX_NAME = 12; - public static final int INDEX_EMAIL = 13; - public static final int INDEX_COMMENT = 14; - - public KeyAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - - mContext = context; - mInflater = LayoutInflater.from(context); - } - - public void setSearchQuery(String query) { - mQuery = query; - } - - public static class KeyItemViewHolder { - public View mView; - public View mLayoutData; - public Long mMasterKeyId; - public TextView mMainUserId; - public TextView mMainUserIdRest; - public TextView mCreationDate; - public ImageView mStatus; - - public KeyItem mDisplayedItem; - - public KeyItemViewHolder(View view) { - mView = view; - mLayoutData = view.findViewById(R.id.key_list_item_data); - mMainUserId = view.findViewById(R.id.key_list_item_name); - mMainUserIdRest = view.findViewById(R.id.key_list_item_email); - mStatus = view.findViewById(R.id.key_list_item_status_icon); - mCreationDate = view.findViewById(R.id.key_list_item_creation); - } - - public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) { - mDisplayedItem = item; - - { // set name and stuff, common to both key types - OpenPgpUtils.UserId userIdSplit = item.mUserId; - if (userIdSplit.name != null) { - mMainUserId.setText(highlighter.highlight(userIdSplit.name)); - } else { - mMainUserId.setText(R.string.user_id_no_name); - } - if (userIdSplit.email != null) { - mMainUserIdRest.setText(highlighter.highlight(userIdSplit.email)); - mMainUserIdRest.setVisibility(View.VISIBLE); - } else { - mMainUserIdRest.setVisibility(View.GONE); - } - } - - // sort of a hack: if this item isn't enabled, we make it clickable - // to intercept its click events. either way, no listener! - mView.setClickable(!enabled); - - { // set edit button and status, specific by key type - - mMasterKeyId = item.mKeyId; - - int textColor; - - // Note: order is important! - if (item.mIsRevoked) { - KeyFormattingUtils - .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); - mStatus.setVisibility(View.VISIBLE); - textColor = context.getResources().getColor(R.color.key_flag_gray); - } else if (item.mIsExpired) { - KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); - mStatus.setVisibility(View.VISIBLE); - textColor = context.getResources().getColor(R.color.key_flag_gray); - } else if (!item.mIsSecure) { - KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray); - mStatus.setVisibility(View.VISIBLE); - textColor = context.getResources().getColor(R.color.key_flag_gray); - } else if (item.mIsSecret) { - mStatus.setVisibility(View.GONE); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } else { - // this is a public key - show if it's verified - if (item.mIsVerified) { - KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED); - mStatus.setVisibility(View.VISIBLE); - } else { - KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED); - mStatus.setVisibility(View.VISIBLE); - } - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - if (!enabled) { - textColor = context.getResources().getColor(R.color.key_flag_gray); - } - - mMainUserId.setTextColor(textColor); - mMainUserIdRest.setTextColor(textColor); - - if (item.mHasDuplicate) { - String dateTime = DateUtils.formatDateTime(context, - item.mCreation.getTime(), - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - mCreationDate.setText(context.getString(R.string.label_key_created, - dateTime)); - mCreationDate.setTextColor(textColor); - mCreationDate.setVisibility(View.VISIBLE); - } else { - mCreationDate.setVisibility(View.GONE); - } - } - } - } - - public boolean isEnabled(Cursor cursor) { - return true; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.key_list_item, parent, false); - KeyItemViewHolder holder = new KeyItemViewHolder(view); - view.setTag(holder); - return view; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - Highlighter highlighter = new Highlighter(context, mQuery); - KeyItem item = new KeyItem(cursor); - boolean isEnabled = isEnabled(cursor); - - KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); - h.setData(context, item, highlighter, isEnabled); - } - - public boolean isSecretAvailable(int id) { - if (!mCursor.moveToPosition(id)) { - throw new IllegalStateException("couldn't move cursor to position " + id); - } - - return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0; - } - - public long getMasterKeyId(int id) { - if (!mCursor.moveToPosition(id)) { - throw new IllegalStateException("couldn't move cursor to position " + id); - } - - return mCursor.getLong(INDEX_MASTER_KEY_ID); - } - - @Override - public KeyItem getItem(int position) { - Cursor c = getCursor(); - if (c.isClosed() || !c.moveToPosition(position)) { - return null; - } - return new KeyItem(c); - } - - @Override - public long getItemId(int position) { - Cursor cursor = getCursor(); - // prevent a crash on rapid cursor changes - if (cursor != null && getCursor().isClosed()) { - return 0L; - } - return super.getItemId(position); - } - - // must be serializable for TokenCompleTextView state - public static class KeyItem implements Serializable { - - public final String mUserIdFull; - public final OpenPgpUtils.UserId mUserId; - public final String mName; - public final String mEmail; - public final String mComment; - public final long mKeyId; - public final boolean mHasDuplicate; - public final boolean mHasEncrypt; - public final Date mCreation; - public final String mFingerprint; - public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsSecure, mIsVerified; - - private KeyItem(Cursor cursor) { - String userId = cursor.getString(INDEX_USER_ID); - mUserId = KeyRing.splitUserId(userId); - mName = cursor.getString(INDEX_NAME); - mEmail = cursor.getString(INDEX_EMAIL); - mComment = cursor.getString(INDEX_COMMENT); - mUserIdFull = userId; - mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); - mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0; - mHasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0; - mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); - mFingerprint = KeyFormattingUtils.convertFingerprintToHex( - cursor.getBlob(INDEX_FINGERPRINT)); - mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; - mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; - mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0; - mIsSecure = cursor.getInt(INDEX_IS_SECURE) > 0; - mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0; - } - - public KeyItem(CanonicalizedPublicKeyRing ring) { - CanonicalizedPublicKey key = ring.getPublicKey(); - String userId = key.getPrimaryUserIdWithFallback(); - mUserId = KeyRing.splitUserId(userId); - mName = mUserId.name; - mEmail = mUserId.email; - mComment = mUserId.comment; - mUserIdFull = userId; - mKeyId = ring.getMasterKeyId(); - mHasDuplicate = false; - mHasEncrypt = key.getKeyRing().getEncryptIds().size() > 0; - mCreation = key.getCreationTime(); - mFingerprint = KeyFormattingUtils.convertFingerprintToHex( - ring.getFingerprint()); - mIsRevoked = key.isRevoked(); - mIsExpired = key.isExpired(); - mIsSecure = key.isSecure(); - - // these two are actually "don't know"s - mIsSecret = false; - mIsVerified = false; - } - - public String getReadableName() { - if (mName != null) { - return mName; - } else { - return mEmail; - } - } - } - - public static String[] getProjectionWith(String[] projection) { - List list = new ArrayList<>(); - list.addAll(Arrays.asList(PROJECTION)); - list.addAll(Arrays.asList(projection)); - return list.toArray(new String[list.size()]); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java deleted file mode 100644 index 1ee67f2ec..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import android.content.Context; -import android.database.Cursor; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.CheckBox; - -import org.sufficientlysecure.keychain.R; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener { - - private HashSet mSelectedItems = new HashSet<>(); - - public KeySelectableAdapter(Context context, Cursor c, int flags, Set initialChecked) { - super(context, c, flags); - if (initialChecked != null) { - mSelectedItems.addAll(initialChecked); - } - } - - public static class KeySelectableItemViewHolder extends KeyItemViewHolder { - - public CheckBox mCheckbox; - - public KeySelectableItemViewHolder(View view) { - super(view); - mCheckbox = view.findViewById(R.id.selected); - } - - public void setCheckedState(boolean checked) { - mCheckbox.setChecked(checked); - } - - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.key_list_selectable_item, parent, false); - KeySelectableItemViewHolder holder = new KeySelectableItemViewHolder(view); - view.setTag(holder); - return view; - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - super.bindView(view, context, cursor); - - KeySelectableItemViewHolder h = (KeySelectableItemViewHolder) view.getTag(); - h.setCheckedState(mSelectedItems.contains(h.mDisplayedItem.mKeyId)); - - } - - public void setCheckedStates(Set checked) { - mSelectedItems.clear(); - mSelectedItems.addAll(checked); - notifyDataSetChanged(); - } - - public Set getSelectedMasterKeyIds() { - return Collections.unmodifiableSet(mSelectedItems); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - long masterKeyId = getMasterKeyId(position); - if (mSelectedItems.contains(masterKeyId)) { - mSelectedItems.remove(masterKeyId); - } else { - mSelectedItems.add(masterKeyId); - } - notifyDataSetChanged(); - } - -} From 4a6e89a50352b133f71d71c0c9026830349858d9 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 28 Jun 2018 18:36:40 +0200 Subject: [PATCH 061/124] aside: add shortcuts --- OpenKeychain/src/debug/res/xml/shortcuts.xml | 25 ++++++++++++++++++++ OpenKeychain/src/main/AndroidManifest.xml | 2 ++ OpenKeychain/src/main/res/xml/shortcuts.xml | 25 ++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 OpenKeychain/src/debug/res/xml/shortcuts.xml create mode 100644 OpenKeychain/src/main/res/xml/shortcuts.xml diff --git a/OpenKeychain/src/debug/res/xml/shortcuts.xml b/OpenKeychain/src/debug/res/xml/shortcuts.xml new file mode 100644 index 000000000..6446091c1 --- /dev/null +++ b/OpenKeychain/src/debug/res/xml/shortcuts.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 4bde8a2aa..0487cd6a3 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -119,6 +119,8 @@ + + + + + + + + + + \ No newline at end of file From 016e7d68eeb173565c0a2aad51cb9807b432df17 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 29 Jun 2018 23:38:29 +0200 Subject: [PATCH 062/124] add debug actions activity (reachable from shortcuts) --- OpenKeychain/src/debug/res/xml/shortcuts.xml | 11 ++ OpenKeychain/src/main/AndroidManifest.xml | 6 + .../remote/ApiPendingIntentFactory.java | 12 +- .../keychain/ui/DebugActionsActivity.java | 157 ++++++++++++++++++ OpenKeychain/src/main/res/values/strings.xml | 3 + OpenKeychain/src/main/res/values/styles.xml | 9 + 6 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java diff --git a/OpenKeychain/src/debug/res/xml/shortcuts.xml b/OpenKeychain/src/debug/res/xml/shortcuts.xml index 6446091c1..10abca10b 100644 --- a/OpenKeychain/src/debug/res/xml/shortcuts.xml +++ b/OpenKeychain/src/debug/res/xml/shortcuts.xml @@ -22,4 +22,15 @@ android:targetClass="org.sufficientlysecure.keychain.ui.EncryptFilesActivity" android:targetPackage="org.sufficientlysecure.keychain.debug" /> + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 0487cd6a3..b5ad62cd2 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -967,6 +967,12 @@ android:name=".remote.ui.RemoteDisplayTransferCodeActivity" android:theme="@style/Theme.Keychain.Transparent"/> + + + + + + missingEmails, - ArrayList duplicateEmails, boolean noUserIdsCheck) { + public PendingIntent createSelectPublicKeyPendingIntent(Intent data, long[] keyIdsArray, + ArrayList missingEmails, + ArrayList duplicateEmails, boolean noUserIdsCheck) { Intent intent = new Intent(mContext, RemoteSelectPubKeyActivity.class); intent.putExtra(RemoteSelectPubKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); intent.putExtra(RemoteSelectPubKeyActivity.EXTRA_NO_USER_IDS_CHECK, noUserIdsCheck); @@ -121,7 +122,7 @@ public class ApiPendingIntentFactory { return createInternal(data, intent); } - PendingIntent createRequestKeyPermissionPendingIntent(Intent data, String packageName, long... masterKeyIds) { + public PendingIntent createRequestKeyPermissionPendingIntent(Intent data, String packageName, long... masterKeyIds) { Intent intent = new Intent(mContext, RequestKeyPermissionActivity.class); intent.putExtra(RequestKeyPermissionActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(RequestKeyPermissionActivity.EXTRA_REQUESTED_KEY_IDS, masterKeyIds); @@ -135,7 +136,8 @@ public class ApiPendingIntentFactory { return createInternal(data, intent); } - PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) { + public PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, + String preferredUserId) { Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class); intent.putExtra(SelectSignKeyIdActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId); @@ -143,7 +145,7 @@ public class ApiPendingIntentFactory { return createInternal(data, intent); } - PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName, + public PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName, byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) { Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_NAME, packageName); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java new file mode 100644 index 000000000..e4bf1286e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + + +import java.util.ArrayList; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender.SendIntentException; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.sqlite.SQLiteConstraintException; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.BuildConfig; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.remote.ApiPendingIntentFactory; +import timber.log.Timber; + + +@TargetApi(VERSION_CODES.LOLLIPOP) +public class DebugActionsActivity extends Activity { + private byte[] packageSig; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + packageSig = registerSelfAsApiApp(); + + setContentView(createView()); + } + + private View createView() { + Context context = getBaseContext(); + + LinearLayout verticalLayout = new LinearLayout(context); + verticalLayout.setOrientation(LinearLayout.VERTICAL); + verticalLayout.setPadding(0, 40, 0, 0); + + Toolbar toolbar = new Toolbar(this); + toolbar.setTitle("Debug Actions"); + verticalLayout.addView(toolbar, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + + ApiPendingIntentFactory pendingIntentFactory = new ApiPendingIntentFactory(context); + addButtonToLayout(context, verticalLayout, "Select Public Key").setOnClickListener((v) -> { + PendingIntent pendingIntent = pendingIntentFactory.createSelectPublicKeyPendingIntent( + new Intent(), new long[] {}, new ArrayList<>(), new ArrayList<>(), false); + startPendingIntent(pendingIntent); + }); + addButtonToLayout(context, verticalLayout, "Select Signing Key (legacy)").setOnClickListener((v) -> { + PendingIntent pendingIntent = pendingIntentFactory.createSelectSignKeyIdLegacyPendingIntent( + new Intent(), BuildConfig.APPLICATION_ID, "test@openkeychain.org"); + startPendingIntent(pendingIntent); + }); + addButtonToLayout(context, verticalLayout, "Select Signing Key").setOnClickListener((v) -> { + PendingIntent pendingIntent = pendingIntentFactory.createSelectSignKeyIdPendingIntent( + new Intent(), BuildConfig.APPLICATION_ID, packageSig, "test@openkeychain.org", false); + startPendingIntent(pendingIntent); + }); + addButtonToLayout(context, verticalLayout, "Select Signing Key (Autocrypt)").setOnClickListener((v) -> { + PendingIntent pendingIntent = pendingIntentFactory.createSelectSignKeyIdPendingIntent( + new Intent(), BuildConfig.APPLICATION_ID, packageSig, "test@openkeychain.org", true); + startPendingIntent(pendingIntent); + }); + addButtonToLayout(context, verticalLayout, "Request Permission (first secret key)").setOnClickListener((v) -> { + KeyRepository keyRepository = KeyRepository.create(getBaseContext()); + long firstMasterKeyId = keyRepository.getAllUnifiedKeyInfoWithSecret().get(0).master_key_id(); + PendingIntent pendingIntent = pendingIntentFactory.createRequestKeyPermissionPendingIntent( + new Intent(), BuildConfig.APPLICATION_ID, firstMasterKeyId); + startPendingIntent(pendingIntent); + }); + + ScrollView view = new ScrollView(context); + view.addView(verticalLayout, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + return view; + } + + private TextView addButtonToLayout(Context context, ViewGroup buttonContainer, String buttonLabel) { + TextView button = new TextView(context, null, 0, R.style.DebugButton); + button.setText(buttonLabel); + buttonContainer.addView(button, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + return button; + } + + private byte[] registerSelfAsApiApp() { + try { + PackageManager packageManager = getPackageManager(); + ApiAppDao apiAppDao = ApiAppDao.getInstance(getBaseContext()); + @SuppressLint("PackageManagerGetSignatures") + byte[] packageSig = packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES).signatures[0].toByteArray(); + apiAppDao.insertApiApp(ApiApp.create(BuildConfig.APPLICATION_ID, packageSig)); + return packageSig; + } catch (NameNotFoundException e) { + throw new AssertionError(e); + } + } + + private void startPendingIntent(PendingIntent pendingIntent) { + try { + startIntentSenderForResult(pendingIntent.getIntentSender(), 0, null, 0, 0, 0); + } catch (SendIntentException e) { + Timber.e(e); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (data != null) { + if (resultCode == RESULT_OK) { + Timber.d("result: ok, intent: %s, extras: %s", data.toString(), data.getExtras()); + } else { + Timber.d("result: cancelled, intent: %s, extras: %s", data.toString(), data.getExtras()); + } + } else { + if (resultCode == RESULT_OK) { + Timber.d("result: ok, intent: null"); + } else { + Timber.d("result: cancelled, intent: null"); + } + } + } +} diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index da278d2b2..cc0fe24ab 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -108,6 +108,9 @@ "Saved!" "Not matching" + + Debug Actions + "Encrypt files" "Exchange keys" diff --git a/OpenKeychain/src/main/res/values/styles.xml b/OpenKeychain/src/main/res/values/styles.xml index b70093b7a..4743e1eae 100644 --- a/OpenKeychain/src/main/res/values/styles.xml +++ b/OpenKeychain/src/main/res/values/styles.xml @@ -84,4 +84,13 @@ - + + From a2eb6349b8531de21657eb4fd33fd54580bdd3a7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 29 Jun 2018 23:38:53 +0200 Subject: [PATCH 063/124] inserting api apps redundantly is fine, as long as the signature matches --- .../keychain/provider/ApiAppDao.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java index 9e9b91493..9b2377489 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.provider; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -66,9 +67,16 @@ public class ApiAppDao extends AbstractDao { } public void insertApiApp(ApiApp apiApp) { + ApiApp existingApiApp = getApiApp(apiApp.package_name()); + if (existingApiApp != null) { + if (!Arrays.equals(existingApiApp.package_signature(), apiApp.package_signature())) { + throw new IllegalStateException("Inserting existing api with different signature?!"); + } + return; + } InsertApiApp statement = new ApiAppsModel.InsertApiApp(getWritableDb()); statement.bind(apiApp.package_name(), apiApp.package_signature()); - statement.execute(); + statement.executeInsert(); } public void deleteApiApp(String packageName) { From 471aa34d3819026fbc31655a0dea13c48107c24b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 29 Jun 2018 23:39:33 +0200 Subject: [PATCH 064/124] show all keys for SelectPublicKeyFragment --- .../keychain/remote/ui/SelectPublicKeyFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index c9726bd40..18c8c5c2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -81,7 +81,7 @@ public class SelectPublicKeyFragment extends RecyclerFragment @NonNull private List loadSortedUnifiedKeyInfo() { - List keyInfos = keyRepository.getAllUnifiedKeyInfoWithSecret(); + List keyInfos = keyRepository.getAllUnifiedKeyInfo(); Collections.sort(keyInfos, sortKeysByPreselectionComparator()); return keyInfos; } From 39f5c5fd03a14f676502f2c3572eca331ca29965 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 29 Jun 2018 23:39:55 +0200 Subject: [PATCH 065/124] ignore redundantly inserted allowed api keys --- .../org/sufficientlysecure/keychain/ApiAllowedKeys.sq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq index 7bdd693cd..ea9b0b1fe 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/ApiAllowedKeys.sq @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS api_allowed_keys ( ); insertAllowedKey: -INSERT INTO api_allowed_keys (package_name, key_id) VALUES (?, ?); +INSERT OR IGNORE INTO api_allowed_keys (package_name, key_id) VALUES (?, ?); deleteByPackageName: DELETE FROM api_allowed_keys From 225b11c1db42af11e8899a5d7137c3b779bb93f0 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 30 Jun 2018 12:31:33 +0200 Subject: [PATCH 066/124] more debug actions --- .../remote/ApiPendingIntentFactory.java | 4 +- .../keychain/ui/DebugActionsActivity.java | 54 ++++++++++++++----- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 6d15e523c..9dfb8f1ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -104,7 +104,7 @@ public class ApiPendingIntentFactory { return createInternal(data, intent); } - PendingIntent createDeduplicatePendingIntent(String packageName, Intent data, ArrayList duplicateEmails) { + public PendingIntent createDeduplicatePendingIntent(String packageName, Intent data, ArrayList duplicateEmails) { Intent intent = new Intent(mContext, RemoteDeduplicateActivity.class); intent.putExtra(RemoteDeduplicateActivity.EXTRA_PACKAGE_NAME, packageName); @@ -219,7 +219,7 @@ public class ApiPendingIntentFactory { } } - PendingIntent createRegisterPendingIntent(Intent data, String packageName, byte[] packageCertificate) { + public PendingIntent createRegisterPendingIntent(Intent data, String packageName, byte[] packageCertificate) { Intent intent = new Intent(mContext, RemoteRegisterActivity.class); intent.putExtra(RemoteRegisterActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(RemoteRegisterActivity.EXTRA_PACKAGE_SIGNATURE, packageCertificate); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java index e4bf1286e..441655761 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java @@ -27,9 +27,9 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.database.sqlite.SQLiteConstraintException; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.support.annotation.Nullable; @@ -43,23 +43,34 @@ import android.widget.ScrollView; import android.widget.TextView; import org.sufficientlysecure.keychain.BuildConfig; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.provider.ApiAppDao; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.remote.ApiPendingIntentFactory; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import timber.log.Timber; @TargetApi(VERSION_CODES.LOLLIPOP) public class DebugActionsActivity extends Activity { - private byte[] packageSig; + + private ApiPendingIntentFactory pendingIntentFactory; + private ApiAppDao apiAppDao; + private KeyRepository keyRepository; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - packageSig = registerSelfAsApiApp(); + if (!Constants.DEBUG) { + throw new UnsupportedOperationException(); + } + + pendingIntentFactory = new ApiPendingIntentFactory(getBaseContext()); + apiAppDao = ApiAppDao.getInstance(getBaseContext()); + keyRepository = KeyRepository.create(getBaseContext()); setContentView(createView()); } @@ -75,7 +86,15 @@ public class DebugActionsActivity extends Activity { toolbar.setTitle("Debug Actions"); verticalLayout.addView(toolbar, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - ApiPendingIntentFactory pendingIntentFactory = new ApiPendingIntentFactory(context); + addButtonToLayout(context, verticalLayout, "Register ApiApp").setOnClickListener((v) -> { + PendingIntent pendingIntent = pendingIntentFactory.createRegisterPendingIntent( + new Intent(), BuildConfig.APPLICATION_ID, getPackageSig()); + startPendingIntent(pendingIntent); + }); + addButtonToLayout(context, verticalLayout, "Unregister ApiApp").setOnClickListener((v) -> { + apiAppDao.deleteApiApp(BuildConfig.APPLICATION_ID); + Notify.create(DebugActionsActivity.this, "Ok", Style.OK).show(); + }); addButtonToLayout(context, verticalLayout, "Select Public Key").setOnClickListener((v) -> { PendingIntent pendingIntent = pendingIntentFactory.createSelectPublicKeyPendingIntent( new Intent(), new long[] {}, new ArrayList<>(), new ArrayList<>(), false); @@ -88,21 +107,27 @@ public class DebugActionsActivity extends Activity { }); addButtonToLayout(context, verticalLayout, "Select Signing Key").setOnClickListener((v) -> { PendingIntent pendingIntent = pendingIntentFactory.createSelectSignKeyIdPendingIntent( - new Intent(), BuildConfig.APPLICATION_ID, packageSig, "test@openkeychain.org", false); + new Intent(), BuildConfig.APPLICATION_ID, getPackageSig(), "test@openkeychain.org", false); startPendingIntent(pendingIntent); }); addButtonToLayout(context, verticalLayout, "Select Signing Key (Autocrypt)").setOnClickListener((v) -> { PendingIntent pendingIntent = pendingIntentFactory.createSelectSignKeyIdPendingIntent( - new Intent(), BuildConfig.APPLICATION_ID, packageSig, "test@openkeychain.org", true); + new Intent(), BuildConfig.APPLICATION_ID, getPackageSig(), "test@openkeychain.org", true); startPendingIntent(pendingIntent); }); addButtonToLayout(context, verticalLayout, "Request Permission (first secret key)").setOnClickListener((v) -> { - KeyRepository keyRepository = KeyRepository.create(getBaseContext()); long firstMasterKeyId = keyRepository.getAllUnifiedKeyInfoWithSecret().get(0).master_key_id(); PendingIntent pendingIntent = pendingIntentFactory.createRequestKeyPermissionPendingIntent( new Intent(), BuildConfig.APPLICATION_ID, firstMasterKeyId); startPendingIntent(pendingIntent); }); + addButtonToLayout(context, verticalLayout, "Deduplicate (dupl@mugenguild.com)").setOnClickListener((v) -> { + ArrayList duplicateEmails = new ArrayList<>(); + duplicateEmails.add("dupl@mugenguild.com"); + PendingIntent pendingIntent = pendingIntentFactory.createDeduplicatePendingIntent( + BuildConfig.APPLICATION_ID, new Intent(), duplicateEmails); + startPendingIntent(pendingIntent); + }); ScrollView view = new ScrollView(context); view.addView(verticalLayout, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); @@ -116,14 +141,13 @@ public class DebugActionsActivity extends Activity { return button; } - private byte[] registerSelfAsApiApp() { + @SuppressLint("PackageManagerGetSignatures") + private byte[] getPackageSig() { try { PackageManager packageManager = getPackageManager(); - ApiAppDao apiAppDao = ApiAppDao.getInstance(getBaseContext()); - @SuppressLint("PackageManagerGetSignatures") - byte[] packageSig = packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES).signatures[0].toByteArray(); - apiAppDao.insertApiApp(ApiApp.create(BuildConfig.APPLICATION_ID, packageSig)); - return packageSig; + PackageInfo packageInfo = + packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES); + return packageInfo.signatures[0].toByteArray(); } catch (NameNotFoundException e) { throw new AssertionError(e); } @@ -142,12 +166,14 @@ public class DebugActionsActivity extends Activity { super.onActivityResult(requestCode, resultCode, data); if (data != null) { if (resultCode == RESULT_OK) { + Notify.create(DebugActionsActivity.this, "Ok", Style.OK).show(); Timber.d("result: ok, intent: %s, extras: %s", data.toString(), data.getExtras()); } else { Timber.d("result: cancelled, intent: %s, extras: %s", data.toString(), data.getExtras()); } } else { if (resultCode == RESULT_OK) { + Notify.create(DebugActionsActivity.this, "Ok", Style.OK).show(); Timber.d("result: ok, intent: null"); } else { Timber.d("result: cancelled, intent: null"); From fdefd30a4649b14ad9831b2a41d0b89da4de8eb6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 30 Jun 2018 12:31:42 +0200 Subject: [PATCH 067/124] fix NPE in ImportKeysAdapter --- .../keychain/ui/adapter/ImportKeysAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 94003d3eb..7356abc16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener; import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback; import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultListener; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; @@ -90,7 +91,8 @@ public class ImportKeysAdapter extends RecyclerView.Adapter Date: Sat, 30 Jun 2018 12:31:57 +0200 Subject: [PATCH 068/124] make single-choice mode work in KeyChoiceAdapter --- .../keychain/ui/adapter/KeyChoiceAdapter.java | 3 +++ OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml | 1 - .../main/res/layout/api_remote_select_authentication_key.xml | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 107628eab..84a48c00e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -82,6 +82,8 @@ public class KeyChoiceAdapter extends FlexibleAdapter { throw new IllegalStateException("Cannot get active item in single select mode!"); } + clearSelection(); + Integer prevActiveItem = this.activeItem; this.activeItem = newActiveItem; @@ -89,6 +91,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { notifyItemChanged(prevActiveItem); } if (newActiveItem != null) { + toggleSelection(newActiveItem); notifyItemChanged(newActiveItem); } } diff --git a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml index d2b1461b4..b86c109ed 100644 --- a/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml +++ b/OpenKeychain/src/main/res/layout/api_remote_deduplicate.xml @@ -132,7 +132,6 @@ android:layout_height="wrap_content" android:text="Select" android:id="@+id/button_select" - android:enabled="false" style="?buttonBarButtonStyle" />
diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml b/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml index 72dda13b0..953d05ba1 100644 --- a/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml +++ b/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml @@ -116,7 +116,6 @@ android:layout_height="wrap_content" android:text="Select" android:id="@+id/button_select" - android:enabled="false" style="?buttonBarButtonStyle" />
From f3c4b15b17abb73b9a5e54051c20b0c15aef17df Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 12:47:41 +0200 Subject: [PATCH 069/124] add updates to openpgp-api-lib --- extern/openpgp-api-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index c2ddaa76b..3d77d9418 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit c2ddaa76bbb8819dafff55ae4af00ac40c94e6fb +Subproject commit 3d77d941868e81ac2a5d6317c24a3df6739e187a From a31c35a4ade84433b482023b7a156e0073bd78fc Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 13:25:18 +0200 Subject: [PATCH 070/124] refresh on any key change by default --- .../keychain/provider/DatabaseNotifyManager.java | 4 ++++ .../keychain/ui/keyview/GenericViewModel.java | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java index 8be6eb33a..896ebef3e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java @@ -43,6 +43,10 @@ public class DatabaseNotifyManager { contentResolver.notifyChange(uri, null); } + public static Uri getNotifyUriAllKeys() { + return BASE_URI; + } + public static Uri getNotifyUriMasterKeyId(long masterKeyId) { return BASE_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java index ab4054297..1dfeed825 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java @@ -4,9 +4,11 @@ package org.sufficientlysecure.keychain.ui.keyview; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import android.content.Context; +import android.net.Uri; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.livedata.GenericLiveData.GenericDataLoader; +import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; /** A simple generic ViewModel that can be used if exactly one field of data needs to be stored. */ @@ -15,7 +17,7 @@ public class GenericViewModel extends ViewModel { public LiveData getGenericLiveData(Context context, GenericDataLoader func) { if (genericLiveData == null) { - genericLiveData = new GenericLiveData<>(context, null, func); + genericLiveData = new GenericLiveData<>(context, DatabaseNotifyManager.getNotifyUriAllKeys(), func); } // noinspection unchecked return genericLiveData; From 6789aee8d11271065ca54d3fe4e7414dbbff704d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 13:44:07 +0200 Subject: [PATCH 071/124] use notification uris mostly correctly --- .../keychain/livedata/GenericLiveData.java | 15 +++++++++++++-- .../ui/dialog/RemoteDeduplicateActivity.java | 2 +- .../RemoteSelectAuthenticationKeyActivity.java | 2 +- .../ui/dialog/RemoteSelectIdKeyActivity.java | 2 +- .../keychain/ui/DecryptFragment.java | 2 +- .../ui/EncryptModeAsymmetricFragment.java | 4 ++-- .../keychain/ui/MultiUserIdsFragment.java | 2 +- .../keychain/ui/ViewKeyAdvActivity.java | 6 +++--- .../keychain/ui/ViewKeyAdvShareFragment.java | 2 +- .../keychain/ui/keyview/KeyFragmentViewModel.java | 8 ++++---- .../keychain/ui/keyview/LinkedIdViewFragment.java | 6 +++--- 11 files changed, 31 insertions(+), 20 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java index f46c731db..04d0348ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java @@ -4,14 +4,25 @@ package org.sufficientlysecure.keychain.livedata; import android.content.Context; import android.net.Uri; +import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; public class GenericLiveData extends AsyncTaskLiveData { private GenericDataLoader genericDataLoader; - public GenericLiveData(Context context, Uri uri, GenericDataLoader genericDataLoader) { - super(context, uri); + public GenericLiveData(Context context, GenericDataLoader genericDataLoader) { + super(context, null); + this.genericDataLoader = genericDataLoader; + } + + public GenericLiveData(Context context, Uri notifyUri, GenericDataLoader genericDataLoader) { + super(context, notifyUri); + this.genericDataLoader = genericDataLoader; + } + + public GenericLiveData(Context context, long notifyMasterKeyId, GenericDataLoader genericDataLoader) { + super(context, DatabaseNotifyManager.getNotifyUriMasterKeyId(notifyMasterKeyId)); this.genericDataLoader = genericDataLoader; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java index acbb2754a..678afe03a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java @@ -100,7 +100,7 @@ public class RemoteDeduplicateActivity extends FragmentActivity { public LiveData> getKeyInfoLiveData(Context context) { if (keyInfoLiveData == null) { - keyInfoLiveData = new GenericLiveData<>(context, null, () -> { + keyInfoLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); return keyRepository.getUnifiedKeyInfosByMailAddress(duplicateAddress); }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java index eecb21837..3385faa80 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java @@ -106,7 +106,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { public LiveData> getKeyInfoLiveData(Context context) { if (keyInfoLiveData == null) { - keyInfoLiveData = new GenericLiveData<>(context, null, () -> { + keyInfoLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); return keyRepository.getAllUnifiedKeyInfoWithSecret(); }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java index 790a830d2..9cc58c6c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java @@ -135,7 +135,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { public LiveData> getSecretUnifiedKeyInfo(Context context) { if (keyInfo == null) { KeyRepository keyRepository = KeyRepository.create(context); - keyInfo = new GenericLiveData<>(context, null, keyRepository::getAllUnifiedKeyInfoWithSecret); + keyInfo = new GenericLiveData<>(context, keyRepository::getAllUnifiedKeyInfoWithSecret); } return keyInfo; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index f0fdb33c8..7fb237427 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -260,7 +260,7 @@ public abstract class DecryptFragment extends Fragment { return; } - unifiedKeyInfoLiveData = new GenericLiveData<>(requireContext(), null, () -> { + unifiedKeyInfoLiveData = new GenericLiveData<>(requireContext(), () -> { KeyRepository keyRepository = KeyRepository.create(requireContext()); Long masterKeyId = keyRepository.getMasterKeyIdBySubkeyId(mSignatureResult.getKeyId()); return keyRepository.getUnifiedKeyInfo(masterKeyId); 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 21aadc67d..2ed681b9c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -155,7 +155,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { LiveData> getSignKeyLiveData(Context context) { if (signKeyLiveData == null) { - signKeyLiveData = new GenericLiveData<>(context, null, () -> { + signKeyLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); return keyRepository.getAllUnifiedKeyInfoWithSecret(); }); @@ -165,7 +165,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { LiveData> getEncryptRecipientLiveData(Context context) { if (encryptRecipientLiveData == null) { - encryptRecipientLiveData = new GenericLiveData<>(context, null, () -> { + encryptRecipientLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); List keyInfos = keyRepository.getAllUnifiedKeyInfo(); ArrayList result = new ArrayList<>(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java index 6cabeb2a8..e9c31026b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -84,7 +84,7 @@ public class MultiUserIdsFragment extends Fragment { KeyRepository keyRepository = KeyRepository.create(activity); LiveData> userIdLiveData = - new GenericLiveData<>(getContext(), null, () -> keyRepository.getUserIds(pubMasterKeyIds)); + new GenericLiveData<>(getContext(), () -> keyRepository.getUserIds(pubMasterKeyIds)); userIdLiveData.observe(this, this::onUserIdsLoaded); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index b79645823..a661ff154 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -127,7 +127,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList } if (unifiedKeyInfoLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); - unifiedKeyInfoLiveData = new GenericLiveData<>(context, null, + unifiedKeyInfoLiveData = new GenericLiveData<>(context, masterKeyId, () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); } return unifiedKeyInfoLiveData; @@ -137,7 +137,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList if (subKeyLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); subKeyLiveData = Transformations.switchMap(getUnifiedKeyInfoLiveData(context), - (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + (unifiedKeyInfo) -> new GenericLiveData<>(context, () -> keyRepository.getSubKeysByMasterKeyId(unifiedKeyInfo.master_key_id()))); } return subKeyLiveData; @@ -147,7 +147,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList if (userIdsLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); userIdsLiveData = Transformations.switchMap(getUnifiedKeyInfoLiveData(context), - (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + (unifiedKeyInfo) -> new GenericLiveData<>(context, () -> keyRepository.getUserIds(unifiedKeyInfo.master_key_id()))); } return userIdsLiveData; 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 0d8a53bde..81c23e805 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -292,7 +292,7 @@ public class ViewKeyAdvShareFragment extends Fragment { unifiedKeyInfoLiveData.observe(this, this::onLoadUnifiedKeyInfo); LiveData qrCodeLiveData = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(getContext(), null, + (unifiedKeyInfo) -> new GenericLiveData<>(getContext(), () -> { String fingerprintHex = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); Uri uri = new Uri.Builder().scheme(Constants.FINGERPRINT_SCHEME).opaquePart(fingerprintHex).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java index 8144caa9c..d2680380b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java @@ -31,7 +31,7 @@ public class KeyFragmentViewModel extends ViewModel { if (identityInfo == null) { IdentityDao identityDao = IdentityDao.getInstance(context); identityInfo = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + (unifiedKeyInfo) -> new GenericLiveData<>(context, () -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id(), showLinkedIds))); } return identityInfo; @@ -41,7 +41,7 @@ public class KeyFragmentViewModel extends ViewModel { if (subkeyStatus == null) { SubkeyStatusDao subkeyStatusDao = SubkeyStatusDao.getInstance(context); subkeyStatus = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + (unifiedKeyInfo) -> new GenericLiveData<>(context, () -> subkeyStatusDao.getSubkeyStatus(unifiedKeyInfo.master_key_id()))); } return subkeyStatus; @@ -51,7 +51,7 @@ public class KeyFragmentViewModel extends ViewModel { if (systemContactInfo == null) { SystemContactDao systemContactDao = SystemContactDao.getInstance(context); systemContactInfo = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + (unifiedKeyInfo) -> new GenericLiveData<>(context, () -> systemContactDao.getSystemContactInfo(unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()))); } @@ -62,7 +62,7 @@ public class KeyFragmentViewModel extends ViewModel { if (keyserverStatus == null) { KeyMetadataDao keyMetadataDao = KeyMetadataDao.create(context); keyserverStatus = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, null, + (unifiedKeyInfo) -> new GenericLiveData<>(context, () -> keyMetadataDao.getKeyMetadata(unifiedKeyInfo.master_key_id()))); } return keyserverStatus; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index 664946a9a..902cd7e05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -497,7 +497,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB LiveData> getCertifyingKeys(Context context) { if (certifyingKeysLiveData == null) { - certifyingKeysLiveData = new GenericLiveData<>(context, null, () -> { + certifyingKeysLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); return keyRepository.getAllUnifiedKeyInfoWithSecret(); }); @@ -508,7 +508,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB LiveData getCertDetails(Context context, long masterKeyId, int lidRank) { if (certDetailsLiveData == null) { CertificationDao certificationDao = CertificationDao.getInstance(context); - certDetailsLiveData = new GenericLiveData<>(context, null, + certDetailsLiveData = new GenericLiveData<>(context, masterKeyId, () -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank)); } return certDetailsLiveData; @@ -517,7 +517,7 @@ public class LinkedIdViewFragment extends CryptoOperationFragment implements OnB public LiveData getLinkedIdInfo(Context context, long masterKeyId, int lidRank) { if (linkedIfInfoLiveData == null) { IdentityDao identityDao = IdentityDao.getInstance(context); - linkedIfInfoLiveData = new GenericLiveData<>(context, null, + linkedIfInfoLiveData = new GenericLiveData<>(context, masterKeyId, () -> identityDao.getLinkedIdInfo(masterKeyId, lidRank)); } return linkedIfInfoLiveData; From 799eed1ca2970d97d340fea7b12221d04e466446 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 13:44:26 +0200 Subject: [PATCH 072/124] fix NPE in EncryptModeAsymmetricFragment --- .../keychain/ui/EncryptModeAsymmetricFragment.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 2ed681b9c..5692a4ff3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -28,6 +28,7 @@ import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -53,7 +54,7 @@ import timber.log.Timber; public class EncryptModeAsymmetricFragment extends EncryptModeFragment { - KeyRepository mKeyRepository; + KeyRepository keyRepository; private KeySpinner mSignKeySpinner; private ChipsInput mEncryptKeyView; @@ -73,6 +74,13 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { return frag; } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + keyRepository = KeyRepository.create(requireContext()); + } + /** * Inflate the layout for this fragment */ @@ -184,7 +192,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { */ private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) { if (signatureKeyId != null) { - UnifiedKeyInfo unifiedKeyInfo = mKeyRepository.getUnifiedKeyInfo(signatureKeyId); + UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(signatureKeyId); if (unifiedKeyInfo == null) { String beautifyKeyId = KeyFormattingUtils.beautifyKeyId(signatureKeyId); Notify.create(getActivity(), getString(R.string.error_preselect_sign_key, beautifyKeyId), Style.ERROR).show(); @@ -197,7 +205,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { for (long preselectedId : encryptionKeyIds) { try { CanonicalizedPublicKeyRing ring = - mKeyRepository.getCanonicalizedPublicKeyRing(preselectedId); + keyRepository.getCanonicalizedPublicKeyRing(preselectedId); Chip infooo = new Chip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo"); mEncryptKeyView.addChip(infooo); } catch (NotFoundException e) { From 19dfac7fc99ae760e73bf0c301372bc8d68d43a6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 13:53:18 +0200 Subject: [PATCH 073/124] querying by key id shouldn't care about revoked keys --- .../main/sqldelight/org/sufficientlysecure/keychain/Keys.sq | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index 515481d19..d2b902843 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -46,11 +46,11 @@ SELECT * FROM unifiedKeyView selectUnifiedKeyInfoByMasterKeyId: SELECT * FROM unifiedKeyView - WHERE is_revoked = 0 AND master_key_id = ?; + WHERE master_key_id = ?; selectUnifiedKeyInfoByMasterKeyIds: SELECT * FROM unifiedKeyView - WHERE is_revoked = 0 AND master_key_id IN ?; + WHERE master_key_id IN ?; selectUnifiedKeyInfoSearchMailAddress: SELECT * FROM unifiedKeyView From a02251274b2e7e0f887da39026a0c4b23f63cd44 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 14:06:06 +0200 Subject: [PATCH 074/124] preselect key if only a single one is available --- .../org/sufficientlysecure/keychain/ui/widget/KeySpinner.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index 27be55b8a..8ac45c6d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -101,6 +101,10 @@ public class KeySpinner extends AppCompatSpinner { } private void maybeSelectPreselection(List keyInfos) { + if (spinnerAdapter.hasNoneItem() && keyInfos.size() == 1) { + setSelection(1); + return; + } if (preSelectedKeyId == null) { return; } From a7e6dc35a8ecffd55a11fde3b69ddbb2caa0dd7e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 14:36:17 +0200 Subject: [PATCH 075/124] clean up KeySignatures.sq --- .../keychain/model/KeySignature.java | 13 +++++++++++++ .../keychain/provider/KeyRepository.java | 5 +++-- .../org/sufficientlysecure/keychain/Certs.sq | 1 - .../sufficientlysecure/keychain/KeySignatures.sq | 7 +++++-- .../org/sufficientlysecure/keychain/Keys.sq | 5 ----- 5 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java new file mode 100644 index 000000000..ed3caa1bc --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/KeySignature.java @@ -0,0 +1,13 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.KeySignaturesModel; + + +@AutoValue +public abstract class KeySignature implements KeySignaturesModel { + public static final Factory FACTORY = new Factory<>(AutoValue_KeySignature::new); + + public static final Mapper MAPPER = new Mapper<>(FACTORY); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index 5eb4f1ad8..5480a0e9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -31,6 +31,7 @@ import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.model.Certification; import org.sufficientlysecure.keychain.model.KeyRingPublic; +import org.sufficientlysecure.keychain.model.KeySignature; import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket; @@ -132,8 +133,8 @@ public class KeyRepository extends AbstractDao { public List getMasterKeyIdsBySigner(List signerMasterKeyIds) { long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds); - SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); - return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map); + SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds); + return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper()::map); } public Long getMasterKeyIdBySubkeyId(long subKeyId) { diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq index 6f7d3ec1e..b35ac0405 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Certs.sq @@ -1,7 +1,6 @@ import java.lang.Integer; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; --- TODO implement. this is only here for reference in SQLDelight CREATE TABLE IF NOT EXISTS certs( master_key_id INTEGER NOT NULL, rank INTEGER NOT NULL, diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq index 1b61dadf3..58cc4b0c2 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/KeySignatures.sq @@ -2,6 +2,9 @@ CREATE TABLE IF NOT EXISTS key_signatures ( master_key_id INTEGER NOT NULL, signer_key_id INTEGER NOT NULL, PRIMARY KEY(master_key_id, signer_key_id), - FOREIGN KEY(master_key_id) REFERENCES - keyrings_public(master_key_id) ON DELETE CASCADE + FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE ); + +selectMasterKeyIdsBySigner: +SELECT master_key_id + FROM key_signatures WHERE signer_key_id IN ?; \ No newline at end of file diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index d2b902843..be5b21b48 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -94,8 +94,3 @@ SELECT key_id FROM keys WHERE is_revoked = 0 AND is_secure = 1 AND has_secret > 1 AND ( expiry IS NULL OR expiry >= date('now') ) AND can_authenticate = 1 AND master_key_id = ?; - --- TODO move to KeySignatures.sq -selectMasterKeyIdsBySigner: -SELECT master_key_id - FROM key_signatures WHERE signer_key_id IN ?; \ No newline at end of file From dca5dfe3aa23ac62d3af352224443800e17229ec Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 14:37:05 +0200 Subject: [PATCH 076/124] save all signature relations in KeySignatures table --- .../provider/KeyWritableRepository.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java index 0a6786d76..e62bf721b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -309,15 +309,17 @@ public class KeyWritableRepository extends KeyRepository { } - // do we have a trusted key for this? - if (trustedKeys.indexOfKey(certId) < 0) { - if (!signerKeyIds.contains(certId)) { - operations.add(ContentProviderOperation.newInsert(KeySignatures.CONTENT_URI) - .withValue(KeySignatures.MASTER_KEY_ID, masterKeyId) - .withValue(KeySignatures.SIGNER_KEY_ID, certId) - .build()); - signerKeyIds.add(certId); - } + // keep a note about the issuer of this key signature + if (!signerKeyIds.contains(certId)) { + operations.add(ContentProviderOperation.newInsert(KeySignatures.CONTENT_URI) + .withValue(KeySignatures.MASTER_KEY_ID, masterKeyId) + .withValue(KeySignatures.SIGNER_KEY_ID, certId) + .build()); + signerKeyIds.add(certId); + } + + boolean isSignatureFromTrustedKey = trustedKeys.indexOfKey(certId) >= 0; + if (!isSignatureFromTrustedKey) { unknownCerts += 1; continue; } From 8ee65f9b04f3783285bd39b940537b8ece26855c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 14:56:14 +0200 Subject: [PATCH 077/124] clean up CertifyKeyFragment --- .../keychain/ui/CertifyKeyFragment.java | 80 +++++++++---------- 1 file changed, 38 insertions(+), 42 deletions(-) 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 059cc683b..247e16bbc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -28,9 +28,9 @@ import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.graphics.PorterDuff; import android.os.Bundle; +import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ImageView; @@ -52,75 +52,73 @@ import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Preferences; -public class CertifyKeyFragment - extends CachingCryptoOperationFragment { +public class CertifyKeyFragment extends CachingCryptoOperationFragment { + private KeyRepository keyRepository; - private CheckBox mUploadKeyCheckbox; + private CheckBox uploadKeyCheckbox; + private KeySpinner certifyKeySpinner; + private MultiUserIdsFragment multiUserIdsFragment; - private KeySpinner mCertifyKeySpinner; - - private MultiUserIdsFragment mMultiUserIdsFragment; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + keyRepository = KeyRepository.create(requireContext()); + } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + Activity activity = requireActivity(); GenericViewModel viewModel = ViewModelProviders.of(this).get(GenericViewModel.class); - LiveData> liveData = viewModel.getGenericLiveData(requireContext(), () -> { - KeyRepository keyRepository = KeyRepository.create(requireContext()); - return keyRepository.getAllUnifiedKeyInfoWithSecret(); - }); - liveData.observe(this, mCertifyKeySpinner::setData); + LiveData> liveData = viewModel.getGenericLiveData( + requireContext(), keyRepository::getAllUnifiedKeyInfoWithSecret); + liveData.observe(this, certifyKeySpinner::setData); if (savedInstanceState == null) { // preselect certify key id if given - long certifyKeyId = getActivity().getIntent() + long certifyKeyId = activity.getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); if (certifyKeyId != Constants.key.none) { - KeyRepository keyRepository = KeyRepository.create(getContext()); UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(certifyKeyId); if (unifiedKeyInfo != null && unifiedKeyInfo.can_certify()) { - mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); + certifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } } } - OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); + OperationResult result = activity.getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); if (result != null) { // display result from import - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); } } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.certify_key_fragment, null); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.certify_key_fragment, superContainer, false); - mCertifyKeySpinner = view.findViewById(R.id.certify_key_spinner); - mUploadKeyCheckbox = view.findViewById(R.id.sign_key_upload_checkbox); - mMultiUserIdsFragment = (MultiUserIdsFragment) + certifyKeySpinner = view.findViewById(R.id.certify_key_spinner); + uploadKeyCheckbox = view.findViewById(R.id.sign_key_upload_checkbox); + multiUserIdsFragment = (MultiUserIdsFragment) getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); - mCertifyKeySpinner.setShowNone(R.string.choice_select_cert); + certifyKeySpinner.setShowNone(R.string.choice_select_cert); // make certify image gray, like action icons ImageView vActionCertifyImage = view.findViewById(R.id.certify_key_action_certify_image); - vActionCertifyImage.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), + vActionCertifyImage.setColorFilter(FormattingUtils.getColorFromAttr(requireActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View vCertifyButton = view.findViewById(R.id.certify_key_certify_button); - vCertifyButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId(); - if (selectedKeyId == Constants.key.none) { - Notify.create(getActivity(), getString(R.string.select_key_to_certify), - Notify.Style.ERROR).show(); - } else { - cryptoOperation(CryptoInputParcel.createCryptoInputParcel(new Date())); - } + vCertifyButton.setOnClickListener(v -> { + long selectedKeyId = certifyKeySpinner.getSelectedKeyId(); + if (selectedKeyId == Constants.key.none) { + Notify.create(getActivity(), getString(R.string.select_key_to_certify), + Notify.Style.ERROR).show(); + } else { + cryptoOperation(CryptoInputParcel.createCryptoInputParcel(new Date())); } }); @@ -129,22 +127,20 @@ public class CertifyKeyFragment @Override public CertifyActionsParcel createOperationInput() { - // Bail out if there is not at least one user id selected - ArrayList certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions(); + ArrayList certifyActions = multiUserIdsFragment.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { - Notify.create(getActivity(), "No identities selected!", - Notify.Style.ERROR).show(); + Notify.create(getActivity(), "No identities selected!", Notify.Style.ERROR).show(); return null; } - long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId(); + long selectedKeyId = certifyKeySpinner.getSelectedKeyId(); // fill values for this action CertifyActionsParcel.Builder actionsParcel = CertifyActionsParcel.builder(selectedKeyId); actionsParcel.addActions(certifyActions); - if (mUploadKeyCheckbox.isChecked()) { + if (uploadKeyCheckbox.isChecked()) { actionsParcel.setParcelableKeyServer(Preferences.getPreferences(getActivity()).getPreferredKeyserver()); } @@ -157,7 +153,7 @@ public class CertifyKeyFragment @Override public void onQueuedOperationSuccess(CertifyResult result) { // protected by Queueing*Fragment - Activity activity = getActivity(); + Activity activity = requireActivity(); Intent intent = new Intent(); intent.putExtra(CertifyResult.EXTRA_RESULT, result); From eb34f900e41c1af483c04df07dae68a34ad93f95 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 15:07:11 +0200 Subject: [PATCH 078/124] clean up package structure --- .../keychain/AndroidTestHelpers.java | 3 +- .../keychain/actions/CustomActions.java | 2 +- .../keychain/ui/EditKeyTest.java | 2 +- .../{provider => }/KeychainDatabase.java | 14 +------ .../{provider => daos}/AbstractDao.java | 5 ++- .../{provider => daos}/ApiAppDao.java | 3 +- .../{provider => daos}/AutocryptPeerDao.java | 3 +- .../{livedata => daos}/CertificationDao.java | 20 ++++------ .../DatabaseNotifyManager.java | 3 +- .../{provider => daos}/KeyMetadataDao.java | 3 +- .../{provider => daos}/KeyRepository.java | 3 +- .../KeyWritableRepository.java | 5 ++- .../LocalPublicKeyStorage.java | 2 +- .../LocalSecretKeyStorage.java | 6 +-- .../OverriddenWarningsDao.java} | 13 ++++--- .../keychain/keysync/KeyserverSyncWorker.java | 2 +- .../keychain/livedata/GenericLiveData.java | 2 +- .../keychain/operations/BackupOperation.java | 4 +- .../keychain/operations/BaseOperation.java | 2 +- .../operations/BaseReadWriteOperation.java | 2 +- .../operations/BenchmarkOperation.java | 2 +- .../keychain/operations/CertifyOperation.java | 6 +-- .../operations/ChangeUnlockOperation.java | 2 +- .../keychain/operations/DeleteOperation.java | 3 +- .../keychain/operations/EditKeyOperation.java | 6 +-- .../keychain/operations/ImportOperation.java | 4 +- .../operations/InputDataOperation.java | 2 +- .../keychain/operations/KeySyncOperation.java | 4 +- .../KeybaseVerificationOperation.java | 2 +- .../operations/PromoteKeyOperation.java | 5 +-- .../keychain/operations/RevokeOperation.java | 2 +- .../operations/SignEncryptOperation.java | 2 +- .../keychain/operations/UploadOperation.java | 4 +- .../keychain/pgp/CanonicalizedSecretKey.java | 3 +- .../pgp/OpenPgpSignatureResultBuilder.java | 3 +- .../pgp/PgpDecryptVerifyOperation.java | 4 +- .../keychain/pgp/PgpSignEncryptOperation.java | 6 +-- .../keychain/pgp/PgpSignatureChecker.java | 4 +- .../keychain/provider/KeychainContract.java | 14 +++---- .../keychain/provider/KeychainProvider.java | 7 ++-- .../SimpleContentResolverInterface.java | 39 ------------------- .../keychain/remote/ApiPermissionHelper.java | 2 +- .../keychain/remote/AutocryptInteractor.java | 4 +- .../remote/KeychainExternalProvider.java | 9 ++--- .../keychain/remote/OpenPgpService.java | 12 +++--- .../remote/PackageUninstallReceiver.java | 2 +- .../remote/SshAuthenticationService.java | 6 +-- .../remote/ui/AppSettingsActivity.java | 2 +- .../AppSettingsAllowedKeysListFragment.java | 4 +- .../keychain/remote/ui/AppsListFragment.java | 2 +- .../remote/ui/RemoteRegisterPresenter.java | 2 +- .../ui/RequestKeyPermissionPresenter.java | 6 +-- .../remote/ui/SecurityProblemPresenter.java | 13 +++---- .../remote/ui/SelectPublicKeyFragment.java | 2 +- .../ui/SelectSignKeyIdListFragment.java | 4 +- .../ui/dialog/RemoteDeduplicateActivity.java | 2 +- ...RemoteSelectAuthenticationKeyActivity.java | 4 +- .../ui/dialog/RemoteSelectIdKeyActivity.java | 2 +- .../RemoteSelectIdentityKeyPresenter.java | 2 +- .../keychain/service/KeychainService.java | 2 +- .../service/PassphraseCacheService.java | 2 +- .../keychain/ssh/AuthenticationOperation.java | 4 +- .../keychain/ui/BackupRestoreFragment.java | 4 +- .../keychain/ui/CertifyKeyFragment.java | 2 +- .../keychain/ui/CreateKeyFinalFragment.java | 4 +- .../keychain/ui/DebugActionsActivity.java | 4 +- .../keychain/ui/DecryptFragment.java | 2 +- .../keychain/ui/DeleteKeyDialogActivity.java | 4 +- .../ui/EncryptModeAsymmetricFragment.java | 4 +- .../keychain/ui/KeyListFragment.java | 5 +-- .../keychain/ui/MultiUserIdsFragment.java | 2 +- .../keychain/ui/PassphraseDialogActivity.java | 5 +-- .../keychain/ui/SafeSlingerActivity.java | 4 +- .../ui/SecurityTokenOperationActivity.java | 3 +- .../ui/SettingsKeyserverFragment.java | 2 +- .../keychain/ui/ViewKeyAdvActivity.java | 2 +- .../keychain/ui/ViewKeyAdvShareFragment.java | 2 +- .../ui/adapter/ImportKeysAdapter.java | 2 +- .../keychain/ui/keyview/GenericViewModel.java | 3 +- .../ui/keyview/KeyFragmentViewModel.java | 2 +- .../ui/keyview/LinkedIdViewFragment.java | 6 +-- .../ui/keyview/UnifiedKeyInfoViewModel.java | 5 +-- .../keychain/ui/keyview/ViewKeyActivity.java | 4 +- .../keychain/ui/keyview/ViewKeyFragment.java | 2 +- .../ui/keyview/loader/IdentityDao.java | 4 +- .../ui/keyview/loader/SubkeyStatusDao.java | 2 +- .../ui/token/PublicKeyRetrievalLoader.java | 4 +- .../transfer/presenter/TransferPresenter.java | 4 +- .../keychain/util/ContactHelper.java | 2 +- .../AuthenticationOperationTest.java | 4 +- .../operations/BackupOperationTest.java | 2 +- .../operations/BenchmarkOperationTest.java | 2 +- .../operations/CertifyOperationTest.java | 2 +- .../operations/PromoteKeyOperationTest.java | 2 +- .../keychain/pgp/InputDataOperationTest.java | 3 +- .../keychain/pgp/PgpEncryptDecryptTest.java | 4 +- .../keychain/provider/Cv25519Test.java | 1 + .../keychain/provider/EddsaTest.java | 1 + .../keychain/provider/InteropTest.java | 1 + .../provider/KeyRepositorySaveTest.java | 2 + .../remote/KeychainExternalProviderTest.java | 6 +-- .../keychain/ssh/SshPublicKeyTest.java | 4 +- .../support/KeyringTestingHelper.java | 4 +- 103 files changed, 189 insertions(+), 249 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => }/KeychainDatabase.java (96%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/AbstractDao.java (90%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/ApiAppDao.java (98%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/AutocryptPeerDao.java (98%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{livedata => daos}/CertificationDao.java (53%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/DatabaseNotifyManager.java (92%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/KeyMetadataDao.java (95%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/KeyRepository.java (99%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/KeyWritableRepository.java (99%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/LocalPublicKeyStorage.java (98%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider => daos}/LocalSecretKeyStorage.java (95%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/{provider/OverriddenWarningsRepository.java => daos/OverriddenWarningsDao.java} (86%) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java index 1e8e78335..a29479bf2 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/AndroidTestHelpers.java @@ -38,8 +38,7 @@ import com.nispok.snackbar.Snackbar; import org.hamcrest.Matcher; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.util.Notify.Style; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java index 032aae0ad..65d39edb8 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/actions/CustomActions.java @@ -27,7 +27,7 @@ import android.view.View; import com.tokenautocomplete.TokenCompleteTextView; import org.hamcrest.Matcher; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import static android.support.test.InstrumentationRegistry.getTargetContext; diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java index 577126300..7857eab3f 100644 --- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java +++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/EditKeyTest.java @@ -27,7 +27,7 @@ import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.runners.MethodSorters; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import static android.support.test.espresso.Espresso.onView; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java similarity index 96% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java index ac865bfc4..a9da8f018 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain; import java.io.File; @@ -34,17 +34,7 @@ import android.database.SQLException; import android.database.sqlite.SQLiteException; import android.support.annotation.VisibleForTesting; -import org.sufficientlysecure.keychain.ApiAllowedKeysModel; -import org.sufficientlysecure.keychain.ApiAppsModel; -import org.sufficientlysecure.keychain.AutocryptPeersModel; -import org.sufficientlysecure.keychain.CertsModel; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.KeyMetadataModel; -import org.sufficientlysecure.keychain.KeyRingsPublicModel; -import org.sufficientlysecure.keychain.KeySignaturesModel; -import org.sufficientlysecure.keychain.KeysModel; -import org.sufficientlysecure.keychain.OverriddenWarningsModel; -import org.sufficientlysecure.keychain.UserPacketsModel; +import org.sufficientlysecure.keychain.daos.LocalSecretKeyStorage; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AbstractDao.java similarity index 90% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AbstractDao.java index 5eb4bc014..2daedcdcd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AbstractDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AbstractDao.java @@ -1,4 +1,4 @@ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.util.ArrayList; @@ -8,7 +8,8 @@ import android.arch.persistence.db.SupportSQLiteDatabase; import android.arch.persistence.db.SupportSQLiteQuery; import android.database.Cursor; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.KeychainDatabase; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; class AbstractDao { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java similarity index 98% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java index 9b2377489..50a273391 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiAppDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.util.ArrayList; @@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey; import org.sufficientlysecure.keychain.ApiAppsModel; import org.sufficientlysecure.keychain.ApiAppsModel.DeleteByPackageName; import org.sufficientlysecure.keychain.ApiAppsModel.InsertApiApp; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.model.ApiAllowedKey; import org.sufficientlysecure.keychain.model.ApiApp; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java similarity index 98% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java index 878ce54c5..d5df18a0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.util.ArrayList; @@ -33,6 +33,7 @@ import org.sufficientlysecure.keychain.AutocryptPeersModel.InsertPeer; import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateGossipKey; import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateKey; import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateLastSeen; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.model.AutocryptPeer; import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus; import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/CertificationDao.java similarity index 53% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/CertificationDao.java index a8287bcb4..8343a55f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/CertificationDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/CertificationDao.java @@ -1,36 +1,30 @@ -package org.sufficientlysecure.keychain.livedata; +package org.sufficientlysecure.keychain.daos; -import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; import android.database.Cursor; import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.model.Certification; import org.sufficientlysecure.keychain.model.Certification.CertDetails; -import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; -public class CertificationDao { - private final SupportSQLiteDatabase db; - private final DatabaseNotifyManager databaseNotifyManager; - +public class CertificationDao extends AbstractDao { public static CertificationDao getInstance(Context context) { KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context); DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - return new CertificationDao(keychainDatabase.getWritableDatabase(), databaseNotifyManager); + return new CertificationDao(keychainDatabase, databaseNotifyManager); } - private CertificationDao(SupportSQLiteDatabase writableDatabase, DatabaseNotifyManager databaseNotifyManager) { - this.db = writableDatabase; - this.databaseNotifyManager = databaseNotifyManager; + private CertificationDao(KeychainDatabase keychainDatabase, DatabaseNotifyManager databaseNotifyManager) { + super(keychainDatabase, databaseNotifyManager); } public CertDetails getVerifyingCertDetails(long masterKeyId, int userPacketRank) { SqlDelightQuery query = Certification.FACTORY.selectVerifyingCertDetails(masterKeyId, userPacketRank); - try (Cursor cursor = db.query(query)) { + try (Cursor cursor = getReadableDb().query(query)) { if (cursor.moveToFirst()) { return Certification.CERT_DETAILS_MAPPER.map(cursor); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java similarity index 92% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java index 896ebef3e..c2709e614 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java @@ -1,4 +1,4 @@ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import android.content.ContentResolver; @@ -6,7 +6,6 @@ import android.content.Context; import android.net.Uri; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; public class DatabaseNotifyManager { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyMetadataDao.java similarity index 95% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyMetadataDao.java index 0a53ca7ce..bd2c37f3d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyMetadataDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyMetadataDao.java @@ -1,4 +1,4 @@ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.util.ArrayList; @@ -11,6 +11,7 @@ import android.database.Cursor; import com.squareup.sqldelight.SqlDelightQuery; import org.sufficientlysecure.keychain.KeyMetadataModel.ReplaceKeyMetadata; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.model.KeyMetadata; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java similarity index 99% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java index 5480a0e9d..d31340d4d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyRepository.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.io.ByteArrayOutputStream; @@ -29,6 +29,7 @@ import android.support.annotation.WorkerThread; import com.squareup.sqldelight.SqlDelightQuery; import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.model.Certification; import org.sufficientlysecure.keychain.model.KeyRingPublic; import org.sufficientlysecure.keychain.model.KeySignature; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java similarity index 99% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java index e62bf721b..4a4d2a58f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/KeyWritableRepository.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.io.IOException; @@ -32,11 +32,11 @@ import android.content.OperationApplicationException; import android.net.Uri; import android.os.RemoteException; import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; import android.support.v4.util.LongSparseArray; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.CustomColumnAdapters; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; @@ -58,6 +58,7 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalPublicKeyStorage.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/LocalPublicKeyStorage.java similarity index 98% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalPublicKeyStorage.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/LocalPublicKeyStorage.java index ff900f80f..61576e79f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalPublicKeyStorage.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/LocalPublicKeyStorage.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.io.ByteArrayOutputStream; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/LocalSecretKeyStorage.java similarity index 95% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/LocalSecretKeyStorage.java index cda09fde3..76516b24a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/LocalSecretKeyStorage.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import java.io.ByteArrayOutputStream; @@ -30,7 +30,7 @@ import android.content.Context; import okhttp3.internal.Util; -class LocalSecretKeyStorage { +public class LocalSecretKeyStorage { private static final String FORMAT_STR_SECRET_KEY = "0x%016x.sec"; private static final String SECRET_KEYS_DIR_NAME = "secret_keys"; @@ -59,7 +59,7 @@ class LocalSecretKeyStorage { return new File(localSecretKeysDir, keyFilename); } - void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException { + public void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException { File publicKeyFile = getSecretKeyFile(masterKeyId); FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java similarity index 86% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java index 68cf00861..4560ee2a9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.sufficientlysecure.keychain.provider; +package org.sufficientlysecure.keychain.daos; import android.arch.persistence.db.SupportSQLiteDatabase; @@ -27,18 +27,19 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.KeychainDatabase; +import org.sufficientlysecure.keychain.KeychainDatabase.Tables; -public class OverriddenWarningsRepository { +public class OverriddenWarningsDao { private final Context context; private KeychainDatabase keychainDatabase; - public static OverriddenWarningsRepository createOverriddenWarningsRepository(Context context) { - return new OverriddenWarningsRepository(context); + public static OverriddenWarningsDao createOverriddenWarningsRepository(Context context) { + return new OverriddenWarningsDao(context); } - private OverriddenWarningsRepository(Context context) { + private OverriddenWarningsDao(Context context) { this.context = context; } 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 9f935b9d5..cad95eb21 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keysync/KeyserverSyncWorker.java @@ -19,7 +19,7 @@ import org.sufficientlysecure.keychain.operations.KeySyncOperation; import org.sufficientlysecure.keychain.operations.KeySyncParcel; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.util.ResourceUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java index 04d0348ee..bd9d70ff2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/GenericLiveData.java @@ -4,7 +4,7 @@ package org.sufficientlysecure.keychain.livedata; import android.content.Context; import android.net.Uri; -import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; 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 1f5f59a1a..eb613c27c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java @@ -50,8 +50,8 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; 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 60e4e6acd..f757eb6dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java @@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; 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 dccd3abe5..43df20fb1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseReadWriteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseReadWriteOperation.java @@ -23,7 +23,7 @@ import android.os.Parcelable; import android.support.v4.os.CancellationSignal; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; public abstract class BaseReadWriteOperation extends BaseOperation { protected final KeyWritableRepository mKeyWritableRepository; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java index 65edf45cd..353ecc5d1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java @@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; 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 f7be25ec5..b9a657cd8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java @@ -38,9 +38,9 @@ import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyMetadataDao; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java index dd3e29a80..bb14199c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ChangeUnlockOperation.java @@ -29,7 +29,7 @@ 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.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java index 4d5b2e371..31564146d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/DeleteOperation.java @@ -23,14 +23,13 @@ import java.util.Collections; import android.content.Context; import android.support.annotation.NonNull; -import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.UpdateTrustResult; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; 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 ea1b90a04..3b101bc1c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -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.daos.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; 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 bafb9f22c..8612e33f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -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.KeyMetadataDao; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index ca5ec93c7..222e75d7a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -50,7 +50,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.InputDataParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java index d36692117..f332eeee2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeySyncOperation.java @@ -15,8 +15,8 @@ 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.daos.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java index c218248d8..90c569f71 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java @@ -48,7 +48,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; 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 25af114ca..fb4f350b2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java @@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.operations; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; import android.content.Context; import android.support.annotation.NonNull; @@ -36,8 +35,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java index da33761d4..3df9fc4c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java @@ -26,7 +26,7 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.RevokeResult; import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; 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 734ff795b..306003b6e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java @@ -34,7 +34,7 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; 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 37b477655..93e8b912f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java @@ -41,8 +41,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index ac2bcee4b..697e5da7d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -47,8 +47,7 @@ import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuilder; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index ea29c2e9c..033879f51 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -27,8 +27,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult.SenderStatusResult; import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils.UserId; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 57220f9e7..9fdc6362d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -71,8 +71,8 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmPr import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequireAnyDecryptPassphraseBuilder; 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 c9b95cb9e..9d071fc4b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -63,9 +63,9 @@ import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainComp import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainHashAlgorithmTags; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java index 43dabc258..1da7077be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java @@ -38,8 +38,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.pgp.DecryptVerifySecurityProblem.DecryptVerifySecurityProblemBuilder; import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureSigningAlgorithm; import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index e991cc99e..0602d0b70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -24,12 +24,12 @@ import org.sufficientlysecure.keychain.Constants; public class KeychainContract { - interface KeyRingsColumns { + public interface KeyRingsColumns { String MASTER_KEY_ID = "master_key_id"; // not a database id String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob } - interface KeysColumns { + public interface KeysColumns { String MASTER_KEY_ID = "master_key_id"; // not a database id String RANK = "rank"; @@ -51,12 +51,12 @@ public class KeychainContract { String EXPIRY = "expiry"; } - interface KeySignaturesColumns { + public interface KeySignaturesColumns { String MASTER_KEY_ID = "master_key_id"; // not a database id String SIGNER_KEY_ID = "signer_key_id"; } - interface UserPacketsColumns { + public interface UserPacketsColumns { String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID String TYPE = "type"; // not a database id String USER_ID = "user_id"; // not a database id @@ -69,7 +69,7 @@ public class KeychainContract { String IS_REVOKED = "is_revoked"; } - interface CertsColumns { + public interface CertsColumns { String MASTER_KEY_ID = "master_key_id"; String RANK = "rank"; String KEY_ID_CERTIFIER = "key_id_certifier"; @@ -79,12 +79,12 @@ public class KeychainContract { String DATA = "data"; } - interface ApiAppsAllowedKeysColumns { + public interface ApiAppsAllowedKeysColumns { String KEY_ID = "key_id"; // not a database id String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name } - interface OverriddenWarnings { + public interface OverriddenWarnings { String IDENTIFIER = "identifier"; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index ec13f24ca..6efac8a72 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -29,17 +29,18 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; +import org.sufficientlysecure.keychain.KeychainDatabase; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.KeychainDatabase.Tables; import timber.log.Timber; -public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface { +public class KeychainProvider extends ContentProvider { private static final int KEY_RING_KEYS = 201; private static final int KEY_RING_USER_IDS = 202; private static final int KEY_RING_PUBLIC = 203; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java deleted file mode 100644 index 5d18431de..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.provider; - - -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; - -/** This interface contains the principal methods for database access - * from {#android.content.ContentResolver}. It is used to allow substitution - * of a ContentResolver in DAOs. - * - * @see ApiAppDao - */ -public interface SimpleContentResolverInterface { - Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder); - - Uri insert(Uri contentUri, ContentValues values); - - int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs); - - int delete(Uri contentUri, String where, String[] selectionArgs); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java index 365bd92d0..7069cc354 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java @@ -35,7 +35,7 @@ import android.os.Binder; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index d280f6a11..440c28534 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -19,8 +19,8 @@ import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index a92adb802..85b6979c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -38,23 +38,22 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.KeychainDatabase; +import org.sufficientlysecure.keychain.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; -import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.remote.AutocryptInteractor.AutocryptState; import timber.log.Timber; -public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface { +public class KeychainExternalProvider extends ContentProvider { private static final int EMAIL_STATUS = 101; private static final int AUTOCRYPT_STATUS = 201; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 8810b418a..db469587f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -67,12 +67,12 @@ import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SecurityProblem; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; -import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; +import org.sufficientlysecure.keychain.daos.OverriddenWarningsDao; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult; import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; @@ -470,7 +470,7 @@ public class OpenPgpService extends Service { SecurityProblem prioritySecurityProblem = securityProblem.getPrioritySecurityProblem(); if (prioritySecurityProblem.isIdentifiable()) { String identifier = prioritySecurityProblem.getIdentifier(); - boolean isOverridden = OverriddenWarningsRepository.createOverriddenWarningsRepository(this) + boolean isOverridden = OverriddenWarningsDao.createOverriddenWarningsRepository(this) .isWarningOverridden(identifier); result.putExtra(OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, isOverridden); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java index faa2f805e..2c1013ac9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java @@ -23,7 +23,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; public class PackageUninstallReceiver extends BroadcastReceiver { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java index d9da21e44..590e309f1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java @@ -46,9 +46,9 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ssh.AuthenticationData; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index cd8448f83..1d43a0b70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -37,7 +37,7 @@ import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index 80682729c..0a493ab44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -28,8 +28,8 @@ import android.support.v7.widget.LinearLayoutManager; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index 7f0a24c5f..87aea3bd4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -41,7 +41,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.ApiApp; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java index 22d7dba0a..e87ac4eb1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java @@ -27,7 +27,7 @@ import android.graphics.drawable.Drawable; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.ApiApp; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java index 4787392fe..700936342 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RequestKeyPermissionPresenter.java @@ -29,9 +29,9 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.remote.ApiPermissionHelper; import org.sufficientlysecure.keychain.remote.ApiPermissionHelper.WrongPackageCertificateException; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java index b7109b1cf..a13be8780 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java @@ -36,8 +36,7 @@ import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; import org.sufficientlysecure.keychain.pgp.SecurityProblem.NotWhitelistedCurve; import org.sufficientlysecure.keychain.pgp.SecurityProblem.EncryptionAlgorithmProblem; import org.sufficientlysecure.keychain.pgp.SecurityProblem.UnidentifiedKeyProblem; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.OverriddenWarningsRepository; +import org.sufficientlysecure.keychain.daos.OverriddenWarningsDao; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; @@ -47,7 +46,7 @@ class SecurityProblemPresenter { private final Context context; private final PackageManager packageManager; - private final OverriddenWarningsRepository overriddenWarningsRepository; + private final OverriddenWarningsDao overriddenWarningsDao; private RemoteSecurityProblemView view; @@ -63,7 +62,7 @@ class SecurityProblemPresenter { SecurityProblemPresenter(Context context) { this.context = context; packageManager = context.getPackageManager(); - overriddenWarningsRepository = OverriddenWarningsRepository.createOverriddenWarningsRepository(context); + overriddenWarningsDao = OverriddenWarningsDao.createOverriddenWarningsRepository(context); } public void setView(RemoteSecurityProblemView view) { @@ -161,7 +160,7 @@ class SecurityProblemPresenter { private void refreshOverrideStatusView() { if (supportOverride) { - if (overriddenWarningsRepository.isWarningOverridden(securityProblemIdentifier)) { + if (overriddenWarningsDao.isWarningOverridden(securityProblemIdentifier)) { view.showOverrideUndoButton(); } else { view.showOverrideButton(); @@ -192,14 +191,14 @@ class SecurityProblemPresenter { overrideCounter++; view.showOverrideMessage(overrideCountLeft); } else { - overriddenWarningsRepository.putOverride(securityProblemIdentifier); + overriddenWarningsDao.putOverride(securityProblemIdentifier); view.finishAsSuppressed(); } } private void resetOverrideStatus() { overrideCounter = 0; - overriddenWarningsRepository.deleteOverride(securityProblemIdentifier); + overriddenWarningsDao.deleteOverride(securityProblemIdentifier); } void onClickGotIt() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index 18c8c5c2a..368893964 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -32,7 +32,7 @@ import android.support.v7.widget.LinearLayoutManager; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index f42b2079e..3cefc75ba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -36,8 +36,8 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java index 678afe03a..057d981a8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicateActivity.java @@ -47,7 +47,7 @@ import com.mikepenz.materialdrawer.util.KeyboardUtil; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicatePresenter.RemoteDeduplicateView; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java index 3385faa80..6266a0af8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java @@ -55,8 +55,8 @@ import org.openintents.ssh.authentication.SshAuthenticationApi; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java index 9cc58c6c4..40820102b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java @@ -64,7 +64,7 @@ import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.livedata.PgpKeyGenerationLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectIdentityKeyPresenter.RemoteSelectIdentityKeyView; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.MainActivity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java index 460546e70..5d3428db3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java @@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.ApiAppDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectIdKeyActivity.RemoteSelectIdViewModel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index db1699a69..2bd2b1b19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -49,7 +49,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index d88e7b74d..8451b942f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.NotificationIds; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java index 31db064cf..9b377809f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java @@ -33,8 +33,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Passphrase; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java index d8fa88491..e4b6271d1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java @@ -39,8 +39,8 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; 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 247e16bbc..29ff936fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; 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 c13b85ede..5eabe1e44 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -48,8 +48,8 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java index 441655761..7be6d945a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java @@ -45,8 +45,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.remote.ApiPendingIntentFactory; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 7fb237427..8e233c7a9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -45,7 +45,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java index 18d99741b..c4fee2953 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui; import java.util.Date; -import java.util.HashMap; import android.app.Activity; import android.app.Dialog; @@ -42,8 +41,7 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.RevokeResult; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; 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 5692a4ff3..d8d9e7629 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -43,8 +43,8 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; 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 24b59291f..68da58389 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -44,7 +44,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ViewAnimator; -import androidx.work.WorkStatus; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; import eu.davidea.fastscroller.FastScroller; @@ -62,9 +61,9 @@ 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.daos.KeyRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDummyItem; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java index e9c31026b..80ffbdf06 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MultiUserIdsFragment.java @@ -37,7 +37,7 @@ import android.widget.ListView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.UserPacket.UserId; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 18ea51a54..1f646294c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -58,9 +58,8 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index c9c8c80a5..26e970955 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -39,8 +39,8 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 74c17ea5b..698d8b515 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -36,8 +36,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.securitytoken.operations.ModifyPinTokenOp; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java index d60734651..1e1f1a86d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -43,7 +43,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; -import org.sufficientlysecure.keychain.provider.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyMetadataDao; import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index a661ff154..cfd455a4e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -48,7 +48,7 @@ import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; 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 81c23e805..5150f0dc6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -58,7 +58,7 @@ import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.ViewKeyAdvActivity.ViewKeyAdvViewModel; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 7356abc16..bcb13376c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -45,7 +45,7 @@ import org.sufficientlysecure.keychain.operations.ImportOperation; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java index 1dfeed825..12a46720f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/GenericViewModel.java @@ -4,11 +4,10 @@ package org.sufficientlysecure.keychain.ui.keyview; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.ViewModel; import android.content.Context; -import android.net.Uri; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.livedata.GenericLiveData.GenericDataLoader; -import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; /** A simple generic ViewModel that can be used if exactly one field of data needs to be stored. */ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java index d2680380b..447d5ff5e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java @@ -11,7 +11,7 @@ import android.content.Context; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.KeyMetadata; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyMetadataDao; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java index 902cd7e05..493403ca5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/LinkedIdViewFragment.java @@ -52,14 +52,14 @@ import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.LinkedResource; import org.sufficientlysecure.keychain.linked.LinkedTokenResource; import org.sufficientlysecure.keychain.linked.UriAttribute; -import org.sufficientlysecure.keychain.livedata.CertificationDao; +import org.sufficientlysecure.keychain.daos.CertificationDao; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.Certification.CertDetails; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java index abc6927fc..69472c8ab 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/UnifiedKeyInfoViewModel.java @@ -8,9 +8,8 @@ import android.net.Uri; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.daos.KeyRepository; public class UnifiedKeyInfoViewModel extends ViewModel { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index dd6cdc865..061796091 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -73,8 +73,8 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index 7df8c2173..7aa2303a7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -45,7 +45,7 @@ import org.sufficientlysecure.keychain.model.KeyMetadata; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter; import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index b02a2ee38..9bc8c7398 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -42,8 +42,8 @@ import org.sufficientlysecure.keychain.model.UserPacket; import org.sufficientlysecure.keychain.model.UserPacket.UserAttribute; import org.sufficientlysecure.keychain.model.UserPacket.UserId; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; import timber.log.Timber; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java index 494694032..2125527f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java @@ -31,7 +31,7 @@ import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.SecurityProblem.KeySecurityProblem; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; public class SubkeyStatusDao { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java index 767d446cf..1257a232e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/token/PublicKeyRetrievalLoader.java @@ -46,8 +46,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.ui.token.PublicKeyRetrievalLoader.KeyRetrievalResult; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ParcelableProxy; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java index 32935fa33..a89a61067 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java @@ -48,8 +48,8 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.Callback; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java index c5361a1ed..64f26fe7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java @@ -49,7 +49,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.model.UserPacket.UserId; -import org.sufficientlysecure.keychain.provider.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; import timber.log.Timber; public class ContactHelper { diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java index 17cea489d..7ca5ee3fe 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java @@ -41,8 +41,8 @@ import org.robolectric.shadows.ShadowLog; import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ssh.AuthenticationData; import org.sufficientlysecure.keychain.ssh.AuthenticationOperation; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java index 1dd1b69bb..e6ec4a097 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BackupOperationTest.java @@ -53,7 +53,7 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow; import org.sufficientlysecure.keychain.pgp.WrappedSignature; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java index 682e689a4..4ec73b25a 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/BenchmarkOperationTest.java @@ -25,7 +25,7 @@ import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.sufficientlysecure.keychain.KeychainTestRunner; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import java.io.PrintStream; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java index 89ca8c060..819329c01 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java @@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java index 83e8db07d..18fe57a78 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java @@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java index 6597fcdb3..2ad3aa659 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java @@ -34,11 +34,10 @@ import org.openintents.openpgp.OpenPgpMetadata; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.sufficientlysecure.keychain.KeychainTestRunner; -import org.sufficientlysecure.keychain.TestHelpers; import org.sufficientlysecure.keychain.operations.InputDataOperation; import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.InputDataParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 39deab677..88215fde2 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -36,7 +36,6 @@ import org.bouncycastle.bcpg.PublicKeyEncSessionPacket; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -54,8 +53,7 @@ import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureBitStrength; import org.sufficientlysecure.keychain.pgp.SecurityProblem.InsecureEncryptionAlgorithm; import org.sufficientlysecure.keychain.pgp.SecurityProblem.MissingMdc; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/Cv25519Test.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/Cv25519Test.java index c87acdcb0..195c4ca5a 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/Cv25519Test.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/Cv25519Test.java @@ -33,6 +33,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.robolectric.util.Util; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java index ddb0100a5..06d0429cd 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java @@ -34,6 +34,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.robolectric.util.Util; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java index 9724a5fc6..5bbbd4833 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java @@ -39,6 +39,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java index c3eb9dca8..1a04345d4 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/KeyRepositorySaveTest.java @@ -30,7 +30,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; +import org.sufficientlysecure.keychain.KeychainDatabase; import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java index f9717fbd2..27ffb3ffd 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/remote/KeychainExternalProviderTest.java @@ -24,10 +24,10 @@ import org.sufficientlysecure.keychain.operations.CertifyOperation; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.ApiAppDao; -import org.sufficientlysecure.keychain.provider.AutocryptPeerDao; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.AutocryptPeerDao; import org.sufficientlysecure.keychain.provider.KeyRepositorySaveTest; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.AutocryptStatus; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java index 5b247c4fc..296d4b1f2 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java @@ -31,8 +31,8 @@ import org.sufficientlysecure.keychain.KeychainTestRunner; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.SshPublicKey; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.util.Passphrase; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java index 284895203..054b7617e 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/support/KeyringTestingHelper.java @@ -37,8 +37,8 @@ import org.bouncycastle.util.Arrays; import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeyWritableRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; /** Helper methods for keyring tests. */ public class KeyringTestingHelper { From 0f473c89d9cc635b7110a71d32021f29daf96f24 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 15:08:45 +0200 Subject: [PATCH 079/124] ditch unused ListFragmentWorkaround --- .../compatibility/ListFragmentWorkaround.java | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java deleted file mode 100644 index 0fa97c8f8..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ListFragmentWorkaround.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.compatibility; - -import android.support.v4.app.ListFragment; -import android.view.View; -import android.widget.ListView; - -/** - * Bug on Android >= 4.1 - *

- * http://code.google.com/p/android/issues/detail?id=35885 - *

- * Items are not checked in layout - */ -public class ListFragmentWorkaround extends ListFragment { - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - l.setItemChecked(position, l.isItemChecked(position)); - } -} From 70be2c8ba1726105987dfaf11d8066a91c367a42 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 15:22:03 +0200 Subject: [PATCH 080/124] clean up OverriddenWarnigsDao --- .../keychain/daos/OverriddenWarningsDao.java | 62 ++++++------------- .../keychain/model/OverriddenWarning.java | 14 +++++ .../keychain/remote/OpenPgpService.java | 2 +- .../remote/ui/SecurityProblemPresenter.java | 2 +- .../keychain/OverriddenWarnings.sq | 14 ++++- 5 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/OverriddenWarning.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java index 4560ee2a9..12554f1a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/OverriddenWarningsDao.java @@ -18,64 +18,42 @@ package org.sufficientlysecure.keychain.daos; -import android.arch.persistence.db.SupportSQLiteDatabase; -import android.arch.persistence.db.SupportSQLiteQuery; -import android.arch.persistence.db.SupportSQLiteQueryBuilder; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings; +import com.squareup.sqldelight.SqlDelightQuery; import org.sufficientlysecure.keychain.KeychainDatabase; -import org.sufficientlysecure.keychain.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.OverriddenWarningsModel.DeleteByIdentifier; +import org.sufficientlysecure.keychain.OverriddenWarningsModel.InsertIdentifier; +import org.sufficientlysecure.keychain.model.OverriddenWarning; -public class OverriddenWarningsDao { - private final Context context; - private KeychainDatabase keychainDatabase; +public class OverriddenWarningsDao extends AbstractDao { + public static OverriddenWarningsDao create(Context context) { + KeychainDatabase database = KeychainDatabase.getInstance(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - public static OverriddenWarningsDao createOverriddenWarningsRepository(Context context) { - return new OverriddenWarningsDao(context); + return new OverriddenWarningsDao(database, databaseNotifyManager); } - private OverriddenWarningsDao(Context context) { - this.context = context; - } - - private KeychainDatabase getDb() { - if (keychainDatabase == null) { - keychainDatabase = KeychainDatabase.getInstance(context); - } - return keychainDatabase; + private OverriddenWarningsDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) { + super(db, databaseNotifyManager); } public boolean isWarningOverridden(String identifier) { - SupportSQLiteDatabase db = getDb().getReadableDatabase(); - SupportSQLiteQuery query = SupportSQLiteQueryBuilder - .builder(Tables.OVERRIDDEN_WARNINGS) - .columns(new String[] { "COUNT(*) FROM " }) - .selection(OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }) - .create(); - Cursor cursor = db.query(query); - - try { - cursor.moveToFirst(); - return cursor.getInt(0) > 0; - } finally { - cursor.close(); - } + SqlDelightQuery query = OverriddenWarning.FACTORY.selectCountByIdentifier(identifier); + Long result = mapSingleRow(query, OverriddenWarning.FACTORY.selectCountByIdentifierMapper()::map); + return result != null && result > 0; } public void putOverride(String identifier) { - SupportSQLiteDatabase db = getDb().getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put(OverriddenWarnings.IDENTIFIER, identifier); - db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv); + InsertIdentifier statement = new InsertIdentifier(getWritableDb()); + statement.bind(identifier); + statement.executeInsert(); } public void deleteOverride(String identifier) { - SupportSQLiteDatabase db = getDb().getWritableDatabase(); - db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }); + DeleteByIdentifier statement = new DeleteByIdentifier(getWritableDb()); + statement.bind(identifier); + statement.executeInsert(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/OverriddenWarning.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/OverriddenWarning.java new file mode 100644 index 000000000..5de6b385b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/OverriddenWarning.java @@ -0,0 +1,14 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.KeySignaturesModel; +import org.sufficientlysecure.keychain.OverriddenWarningsModel; + + +@AutoValue +public abstract class OverriddenWarning implements OverriddenWarningsModel { + public static final Factory FACTORY = new Factory<>(AutoValue_OverriddenWarning::new); + + public static final Mapper MAPPER = new Mapper<>(FACTORY); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index db469587f..b173e65bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -470,7 +470,7 @@ public class OpenPgpService extends Service { SecurityProblem prioritySecurityProblem = securityProblem.getPrioritySecurityProblem(); if (prioritySecurityProblem.isIdentifiable()) { String identifier = prioritySecurityProblem.getIdentifier(); - boolean isOverridden = OverriddenWarningsDao.createOverriddenWarningsRepository(this) + boolean isOverridden = OverriddenWarningsDao.create(this) .isWarningOverridden(identifier); result.putExtra(OpenPgpApi.RESULT_OVERRIDE_CRYPTO_WARNING, isOverridden); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java index a13be8780..f1609f99a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SecurityProblemPresenter.java @@ -62,7 +62,7 @@ class SecurityProblemPresenter { SecurityProblemPresenter(Context context) { this.context = context; packageManager = context.getPackageManager(); - overriddenWarningsDao = OverriddenWarningsDao.createOverriddenWarningsRepository(context); + overriddenWarningsDao = OverriddenWarningsDao.create(context); } public void setView(RemoteSecurityProblemView view) { diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq index 3736a0f85..f5afb017d 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/OverriddenWarnings.sq @@ -1,4 +1,16 @@ CREATE TABLE IF NOT EXISTS overridden_warnings ( _id INTEGER PRIMARY KEY AUTOINCREMENT, identifier TEXT NOT NULL UNIQUE -); \ No newline at end of file +); + +selectCountByIdentifier: +SELECT COUNT(*) + FROM overridden_warnings + WHERE identifier = ?; + +insertIdentifier: +INSERT OR IGNORE INTO overridden_warnings (identifier) VALUES (?); + +deleteByIdentifier: +DELETE FROM overridden_warnings + WHERE identifier = ?; \ No newline at end of file From 5e3e38a3ccb57532ec631a24aff6c498cd1ecc03 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 15:39:05 +0200 Subject: [PATCH 081/124] fix NPE in switchMap LiveData instances --- .../keychain/ui/ViewKeyAdvActivity.java | 4 ++-- .../keychain/ui/ViewKeyAdvShareFragment.java | 2 +- .../keychain/ui/keyview/KeyFragmentViewModel.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index cfd455a4e..71eb650c7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -137,7 +137,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList if (subKeyLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); subKeyLiveData = Transformations.switchMap(getUnifiedKeyInfoLiveData(context), - (unifiedKeyInfo) -> new GenericLiveData<>(context, + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context, () -> keyRepository.getSubKeysByMasterKeyId(unifiedKeyInfo.master_key_id()))); } return subKeyLiveData; @@ -147,7 +147,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements OnPageChangeList if (userIdsLiveData == null) { KeyRepository keyRepository = KeyRepository.create(context); userIdsLiveData = Transformations.switchMap(getUnifiedKeyInfoLiveData(context), - (unifiedKeyInfo) -> new GenericLiveData<>(context, + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context, () -> keyRepository.getUserIds(unifiedKeyInfo.master_key_id()))); } return userIdsLiveData; 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 5150f0dc6..9c80c17d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -292,7 +292,7 @@ public class ViewKeyAdvShareFragment extends Fragment { unifiedKeyInfoLiveData.observe(this, this::onLoadUnifiedKeyInfo); LiveData qrCodeLiveData = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(getContext(), + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(getContext(), () -> { String fingerprintHex = KeyFormattingUtils.convertFingerprintToHex(unifiedKeyInfo.fingerprint()); Uri uri = new Uri.Builder().scheme(Constants.FINGERPRINT_SCHEME).opaquePart(fingerprintHex).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java index 447d5ff5e..3e3c58466 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/KeyFragmentViewModel.java @@ -31,7 +31,7 @@ public class KeyFragmentViewModel extends ViewModel { if (identityInfo == null) { IdentityDao identityDao = IdentityDao.getInstance(context); identityInfo = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context, () -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id(), showLinkedIds))); } return identityInfo; @@ -41,7 +41,7 @@ public class KeyFragmentViewModel extends ViewModel { if (subkeyStatus == null) { SubkeyStatusDao subkeyStatusDao = SubkeyStatusDao.getInstance(context); subkeyStatus = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context, () -> subkeyStatusDao.getSubkeyStatus(unifiedKeyInfo.master_key_id()))); } return subkeyStatus; @@ -51,7 +51,7 @@ public class KeyFragmentViewModel extends ViewModel { if (systemContactInfo == null) { SystemContactDao systemContactDao = SystemContactDao.getInstance(context); systemContactInfo = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context, () -> systemContactDao.getSystemContactInfo(unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()))); } @@ -62,7 +62,7 @@ public class KeyFragmentViewModel extends ViewModel { if (keyserverStatus == null) { KeyMetadataDao keyMetadataDao = KeyMetadataDao.create(context); keyserverStatus = Transformations.switchMap(unifiedKeyInfoLiveData, - (unifiedKeyInfo) -> new GenericLiveData<>(context, + (unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context, () -> keyMetadataDao.getKeyMetadata(unifiedKeyInfo.master_key_id()))); } return keyserverStatus; From cb7a774324becfd752f8e298ab4c4f7cc917c767 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 15:47:20 +0200 Subject: [PATCH 082/124] delete unused FilterCursorWrapper class --- .../keychain/util/FilterCursorWrapper.java | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java deleted file mode 100644 index 583c294ee..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.util; - - -import android.database.Cursor; -import android.database.CursorWrapper; - -public abstract class FilterCursorWrapper extends CursorWrapper { - private int[] mIndex; - private int mCount = 0; - private int mPos = 0; - - public abstract boolean isVisible(Cursor cursor); - - public FilterCursorWrapper(Cursor cursor) { - super(cursor); - mCount = super.getCount(); - mIndex = new int[mCount]; - for (int i = 0; i < mCount; i++) { - super.moveToPosition(i); - if (isVisible(cursor)) { - mIndex[mPos++] = i; - } - } - mCount = mPos; - mPos = 0; - super.moveToFirst(); - } - - @Override - public boolean move(int offset) { - return this.moveToPosition(mPos + offset); - } - - @Override - public boolean moveToNext() { - return this.moveToPosition(mPos + 1); - } - - @Override - public boolean moveToPrevious() { - return this.moveToPosition(mPos - 1); - } - - @Override - public boolean moveToFirst() { - return this.moveToPosition(0); - } - - @Override - public boolean moveToLast() { - return this.moveToPosition(mCount - 1); - } - - @Override - public boolean moveToPosition(int position) { - if (position >= mCount || position < 0) { - return false; - } - return super.moveToPosition(mIndex[position]); - } - - @Override - public int getCount() { - return mCount; - } - - public int getHiddenCount() { - return super.getCount() - mCount; - } - - @Override - public int getPosition() { - return mPos; - } - -} \ No newline at end of file From 2cae2065c06e77b1451bf056486eff24eb56ae2c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 16:07:24 +0200 Subject: [PATCH 083/124] fix date in key choice adapter --- .../keychain/ui/adapter/KeyChoiceAdapter.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 84a48c00e..de3d18c75 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -210,10 +210,15 @@ public class KeyChoiceAdapter extends FlexibleAdapter { vName.setText(keyInfo.name()); Context context = vCreation.getContext(); - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - vCreation.setText(context.getString(R.string.label_key_created, dateTime)); + if (keyInfo.has_any_secret() || keyInfo.has_duplicate()) { + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation() * 1000, + DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | + DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); + vCreation.setText(context.getString(R.string.label_key_created, dateTime)); + vCreation.setVisibility(View.VISIBLE); + } else { + vCreation.setVisibility(View.GONE); + } switch (choiceMode) { case Mode.IDLE: { From f7859e3bce851f3dcd79afaeefdbf503abce75ce Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 16:30:54 +0200 Subject: [PATCH 084/124] first iteration, disable predicate for KeyChoiceAdapter --- .../AppSettingsAllowedKeysListFragment.java | 2 +- .../remote/ui/SelectPublicKeyFragment.java | 4 +- .../keychain/ui/adapter/KeyChoiceAdapter.java | 57 ++++++++++++++----- OpenKeychain/src/main/res/values/strings.xml | 2 + 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index 0a493ab44..7abead888 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -89,7 +89,7 @@ public class AppSettingsAllowedKeysListFragment extends RecyclerFragment data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data); + keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data, null); setAdapter(keyChoiceAdapter); Set checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); keyChoiceAdapter.setSelectionByIds(checkedIds); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index 368893964..1a01edfda 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -123,7 +123,9 @@ public class SelectPublicKeyFragment extends RecyclerFragment public void onLoadUnifiedKeyData(List data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data); + keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data, (keyInfo -> { + return keyInfo.is_revoked() ? R.string.keychoice_cannot_encrypt : null; + })); setAdapter(keyChoiceAdapter); keyChoiceAdapter.setSelectionByIds(selectedMasterKeyIds); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index de3d18c75..b8a6d0701 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -9,12 +9,14 @@ import java.util.Set; import android.content.Context; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; import android.view.View; import android.widget.CheckBox; import android.widget.RadioButton; import android.widget.TextView; +import android.widget.Toast; import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; @@ -26,43 +28,57 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceItem public class KeyChoiceAdapter extends FlexibleAdapter { + @Nullable private final OnKeyClickListener onKeyClickListener; + @Nullable + private final KeyDisabledPredicate keyDisabledPredicate; + @Nullable private Integer activeItem; public static KeyChoiceAdapter createSingleClickableAdapter(List items, OnKeyClickListener onKeyClickListener) { - return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE); + return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE, null); } public static KeyChoiceAdapter createSingleChoiceAdapter(List items) { - return new KeyChoiceAdapter(items, null, Mode.SINGLE); + return new KeyChoiceAdapter(items, null, Mode.SINGLE, null); } - public static KeyChoiceAdapter createMultiChoiceAdapter(List items) { - return new KeyChoiceAdapter(items, null, Mode.MULTI); + public static KeyChoiceAdapter createMultiChoiceAdapter(List items, KeyDisabledPredicate keyDisabledPredicate) { + return new KeyChoiceAdapter(items, null, Mode.MULTI, keyDisabledPredicate); } - private KeyChoiceAdapter(List items, OnKeyClickListener onKeyClickListener, int idle) { - super(getKeyChoiceItems(items)); + private KeyChoiceAdapter(List items, @Nullable OnKeyClickListener onKeyClickListener, int idle, + @Nullable KeyDisabledPredicate keyDisabledPredicate) { + super(getKeyChoiceItems(items, keyDisabledPredicate)); setMode(idle); addListener((OnItemClickListener) (view, position) -> onClickItem(position)); this.onKeyClickListener = onKeyClickListener; + this.keyDisabledPredicate = keyDisabledPredicate; } @Nullable - private static ArrayList getKeyChoiceItems(@Nullable List items) { + private static ArrayList getKeyChoiceItems(@Nullable List items, + @Nullable KeyDisabledPredicate keyDisabledPredicate) { if (items == null) { return null; } ArrayList choiceItems = new ArrayList<>(); for (UnifiedKeyInfo keyInfo : items) { - KeyChoiceItem keyChoiceItem = new KeyChoiceItem(keyInfo); + Integer disabledString = keyDisabledPredicate != null ? keyDisabledPredicate.getDisabledString(keyInfo) : null; + KeyChoiceItem keyChoiceItem = new KeyChoiceItem(keyInfo, disabledString); choiceItems.add(keyChoiceItem); } return choiceItems; } private boolean onClickItem(int position) { + KeyChoiceItem item = getItem(position); + if (item != null && item.disabledStringRes != null) { + Toast.makeText(getRecyclerView().getContext(), item.disabledStringRes, Toast.LENGTH_SHORT).show(); + return false; + } + if (getMode() == Mode.MULTI) { toggleSelection(position); notifyItemChanged(position); @@ -72,8 +88,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { return true; } - KeyChoiceItem item = getItem(position); - onKeyClickListener.onKeyClick(item.keyInfo); + Objects.requireNonNull(onKeyClickListener).onKeyClick(item.keyInfo); return false; } @@ -109,7 +124,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { } public void setUnifiedKeyInfoItems(List keyInfos) { - List keyChoiceItems = getKeyChoiceItems(keyInfos); + List keyChoiceItems = getKeyChoiceItems(keyInfos, keyDisabledPredicate); updateDataSet(keyChoiceItems); } @@ -151,9 +166,12 @@ public class KeyChoiceAdapter extends FlexibleAdapter { public static class KeyChoiceItem extends AbstractFlexibleItem { private UnifiedKeyInfo keyInfo; + @StringRes + private Integer disabledStringRes; - KeyChoiceItem(UnifiedKeyInfo keyInfo) { + KeyChoiceItem(UnifiedKeyInfo keyInfo, @StringRes Integer disabledStringRes) { this.keyInfo = keyInfo; + this.disabledStringRes = disabledStringRes; setSelectable(true); } @@ -171,7 +189,8 @@ public class KeyChoiceAdapter extends FlexibleAdapter { public void bindViewHolder(FlexibleAdapter adapter, KeyChoiceViewHolder holder, int position, List payloads) { boolean isActive = adapter.isSelected(position); - holder.bind(keyInfo, adapter.getMode(), isActive); + boolean isEnabled = disabledStringRes == null; + holder.bind(keyInfo, adapter.getMode(), isActive, isEnabled); } @Override @@ -206,7 +225,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { vRadio = itemView.findViewById(R.id.radio_keychoice); } - void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive) { + void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive, boolean isEnabled) { vName.setText(keyInfo.name()); Context context = vCreation.getContext(); @@ -239,10 +258,20 @@ public class KeyChoiceAdapter extends FlexibleAdapter { break; } } + + vCheckbox.setEnabled(isEnabled); + vRadio.setEnabled(isEnabled); + vName.setEnabled(isEnabled); + vCreation.setEnabled(isEnabled); } } public interface OnKeyClickListener { void onKeyClick(UnifiedKeyInfo keyInfo); } + + public interface KeyDisabledPredicate { + @StringRes + Integer getDisabledString(UnifiedKeyInfo keyInfo); + } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index cc0fe24ab..5ad862968 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -2033,4 +2033,6 @@ Started updating all keys… Key update successful An error occurred while updating all keys + + This key cannot be used for encryption! From 905dd728cc04f1097d126618ef02fa4b25e0d83d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 16:58:44 +0200 Subject: [PATCH 085/124] improve NPE robustnes in AutocryptInteractor --- .../keychain/remote/AutocryptInteractor.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java index 440c28534..6dfa1e143 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AutocryptInteractor.java @@ -89,7 +89,7 @@ public class AutocryptInteractor { // -> This should be taken care of in the mail client that sends us this data! // 2. If peers[gossip-addr].gossip_timestamp is more recent than the message’s effective date, then the update process terminates. - Date lastSeenGossip = currentAutocryptPeer.gossip_last_seen_key(); + Date lastSeenGossip = currentAutocryptPeer != null ? currentAutocryptPeer.gossip_last_seen_key() : null; if (lastSeenGossip != null && lastSeenGossip.after(effectiveDate)) { return; } @@ -172,19 +172,22 @@ public class AutocryptInteractor { @Nullable private AutocryptRecommendationResult determineAutocryptKeyRecommendation(AutocryptKeyStatus autocryptKeyStatus) { - boolean hasKey = autocryptKeyStatus.hasKey(); + AutocryptPeer autocryptPeer = autocryptKeyStatus.autocryptPeer(); + + Long masterKeyId = autocryptPeer.master_key_id(); + boolean hasKey = masterKeyId != null; boolean isRevoked = autocryptKeyStatus.isKeyRevoked(); boolean isExpired = autocryptKeyStatus.isKeyExpired(); if (!hasKey || isRevoked || isExpired) { return null; } - AutocryptPeer autocryptPeer = autocryptKeyStatus.autocryptPeer(); - long masterKeyId = autocryptPeer.master_key_id(); Date lastSeen = autocryptPeer.last_seen(); Date lastSeenKey = autocryptPeer.last_seen_key(); boolean isVerified = autocryptKeyStatus.isKeyVerified(); - if (lastSeenKey.getTime() < (lastSeen.getTime() - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS)) { + boolean isLastSeenOlderThanDiscourageTimespan = lastSeen != null && lastSeenKey != null && + lastSeenKey.getTime() < (lastSeen.getTime() - AUTOCRYPT_DISCOURAGE_THRESHOLD_MILLIS); + if (isLastSeenOlderThanDiscourageTimespan) { return new AutocryptRecommendationResult(autocryptPeer.identifier(), AutocryptState.DISCOURAGED_OLD, masterKeyId, isVerified); } From 60ec78b9ae860d98f6fe8fdc425b58a5be1b8c4a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 16:58:59 +0200 Subject: [PATCH 086/124] always insert autocrypt key before update operations --- .../keychain/daos/AutocryptPeerDao.java | 32 ++++++++++--------- .../keychain/model/AutocryptPeer.java | 4 --- .../keychain/AutocryptPeers.sq | 2 +- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java index d5df18a0d..f70d3638f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/AutocryptPeerDao.java @@ -94,37 +94,39 @@ public class AutocryptPeerDao extends AbstractDao { return result; } + private void ensureAutocryptPeerExists(String packageName, String autocryptId) { + InsertPeer insertStatement = new InsertPeer(getWritableDb()); + insertStatement.bind(packageName, autocryptId); + insertStatement.executeInsert(); + } + public void insertOrUpdateLastSeen(String packageName, String autocryptId, Date date) { + ensureAutocryptPeerExists(packageName, autocryptId); + UpdateLastSeen updateStatement = new UpdateLastSeen(getWritableDb(), AutocryptPeer.FACTORY); updateStatement.bind(packageName, autocryptId, date); - int updated = updateStatement.executeUpdateDelete(); - - if (updated == 0) { - InsertPeer insertStatement = new InsertPeer(getWritableDb(), AutocryptPeer.FACTORY); - insertStatement.bind(packageName, autocryptId, date); - insertStatement.executeInsert(); - } + updateStatement.executeUpdateDelete(); } public void updateKey(String packageName, String autocryptId, Date effectiveDate, long masterKeyId, boolean isMutual) { + ensureAutocryptPeerExists(packageName, autocryptId); + UpdateKey updateStatement = new UpdateKey(getWritableDb(), AutocryptPeer.FACTORY); updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, isMutual); - int rowsUpdated = updateStatement.executeUpdateDelete(); - if (rowsUpdated == 0) { - throw new IllegalStateException("No rows updated! Was this peer inserted before the update?"); - } + updateStatement.executeUpdateDelete(); + getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId); } public void updateKeyGossip(String packageName, String autocryptId, Date effectiveDate, long masterKeyId, GossipOrigin origin) { + ensureAutocryptPeerExists(packageName, autocryptId); + UpdateGossipKey updateStatement = new UpdateGossipKey(getWritableDb(), AutocryptPeer.FACTORY); updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, origin); - int rowsUpdated = updateStatement.executeUpdateDelete(); - if (rowsUpdated == 0) { - throw new IllegalStateException("No rows updated! Was this peer inserted before the update?"); - } + updateStatement.executeUpdateDelete(); + getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java index 1a5c177de..f9c5e1783 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/AutocryptPeer.java @@ -40,10 +40,6 @@ public abstract class AutocryptPeer implements AutocryptPeersModel { return gossip_key_is_verified_int() != 0; } - public boolean hasKey() { - return autocryptPeer().master_key_id() != null; - } - public boolean isKeyRevoked() { Boolean revoked = key_is_revoked_int(); return revoked != null && revoked; diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq index df4506516..103d8523d 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/AutocryptPeers.sq @@ -48,7 +48,7 @@ updateGossipKey: UPDATE autocrypt_peers SET gossip_last_seen_key = ?3, gossip_master_key_id = ?4, gossip_origin = ?5 WHERE package_name = ?1 AND identifier = ?2; insertPeer: -INSERT INTO autocrypt_peers (package_name, identifier, last_seen) VALUES (?, ?, ?); +INSERT OR IGNORE INTO autocrypt_peers (package_name, identifier) VALUES (?, ?); selectAutocryptKeyStatus: SELECT autocryptPeer.*, From 4517d2a7f5bcdf2b8875f0f0f91faffecc06c108 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 17:00:40 +0200 Subject: [PATCH 087/124] fix two more timestamp formats --- .../remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java | 2 +- .../keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java index 6266a0af8..7110af55c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java @@ -339,7 +339,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { vName.setText(keyInfo.name()); Context context = vCreation.getContext(); - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation() * 1000, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java index 40820102b..76a6dd600 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java @@ -523,7 +523,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { vName.setText(context.getString(R.string.use_key_no_name)); } - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation(), + String dateTime = DateUtils.formatDateTime(context, keyInfo.creation() * 1000, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); vCreation.setText(context.getString(R.string.label_key_created, dateTime)); From 5e2330c63f42a56290988f02a414d3eff0deaa10 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 17:07:55 +0200 Subject: [PATCH 088/124] Grant access to right key id after generation --- .../remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java index 5d3428db3..29d9d4c6e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java @@ -221,7 +221,7 @@ class RemoteSelectIdentityKeyPresenter { void onImportOpSuccess(ImportKeyResult result) { long importedMasterKeyId = result.getImportedMasterKeyIds()[0]; apiAppDao.insertApiApp(apiApp); - apiAppDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId); + apiAppDao.addAllowedKeyIdForApp(apiApp.package_name(), importedMasterKeyId); view.finishAndReturn(importedMasterKeyId); } From b7dcb5d98d5eed8327144555ead80fe0c5c394a7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 17:10:48 +0200 Subject: [PATCH 089/124] Stricter null check for selected master key id --- .../remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java index 29d9d4c6e..260f9d2d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.remote.ui.dialog; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import android.arch.lifecycle.LifecycleOwner; import android.content.Context; @@ -55,7 +56,7 @@ class RemoteSelectIdentityKeyPresenter { private List keyInfoData; private UserId userId; - private long selectedMasterKeyId; + private Long selectedMasterKeyId; private byte[] generatedKeyData; private ApiAppDao apiAppDao; private ApiApp apiApp; @@ -214,7 +215,7 @@ class RemoteSelectIdentityKeyPresenter { void onHighlightFinished() { apiAppDao.insertApiApp(apiApp); - apiAppDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId); + apiAppDao.addAllowedKeyIdForApp(apiApp.package_name(), Objects.requireNonNull(selectedMasterKeyId)); view.finishAndReturn(selectedMasterKeyId); } From 7ab2161cbacc7ce5afc5bbebb7a09de01ca47265 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 17:29:56 +0200 Subject: [PATCH 090/124] use correct filtered list for key selection in RemoteSelectidentityPresenter --- .../remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java index 260f9d2d0..abc4695b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java @@ -185,7 +185,7 @@ class RemoteSelectIdentityKeyPresenter { } void onKeyItemClick(int position) { - selectedMasterKeyId = keyInfoData.get(position).master_key_id(); + selectedMasterKeyId = getFilteredKeyInfo().get(position).master_key_id(); view.highlightKey(position); } From bf9179391bbe401dbf807fa5243d05298ed98461 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 19:00:32 +0200 Subject: [PATCH 091/124] Add notification uris to ApiAppDao --- .../keychain/daos/ApiAppDao.java | 8 ++ .../keychain/daos/DatabaseNotifyManager.java | 20 ++- .../keychain/livedata/ApiAppsLiveData.java | 113 +++++++++++++++++ .../keychain/remote/ui/AppsListFragment.java | 117 +++--------------- 4 files changed, 156 insertions(+), 102 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/ApiAppsLiveData.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java index 50a273391..79e87bf81 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/ApiAppDao.java @@ -78,12 +78,16 @@ public class ApiAppDao extends AbstractDao { InsertApiApp statement = new ApiAppsModel.InsertApiApp(getWritableDb()); statement.bind(apiApp.package_name(), apiApp.package_signature()); statement.executeInsert(); + + getDatabaseNotifyManager().notifyApiAppChange(apiApp.package_name()); } public void deleteApiApp(String packageName) { DeleteByPackageName deleteByPackageName = new DeleteByPackageName(getWritableDb()); deleteByPackageName.bind(packageName); deleteByPackageName.executeUpdateDelete(); + + getDatabaseNotifyManager().notifyApiAppChange(packageName); } public HashSet getAllowedKeyIdsForApp(String packageName) { @@ -108,12 +112,16 @@ public class ApiAppDao extends AbstractDao { statement.bind(packageName, keyId); statement.execute(); } + + getDatabaseNotifyManager().notifyApiAppChange(packageName); } public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) { InsertAllowedKey statement = new InsertAllowedKey(getWritableDb()); statement.bind(packageName, allowedKeyId); statement.execute(); + + getDatabaseNotifyManager().notifyApiAppChange(packageName); } public List getAllApiApps() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java index c2709e614..ee81a5f14 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/daos/DatabaseNotifyManager.java @@ -9,7 +9,8 @@ import org.sufficientlysecure.keychain.Constants; public class DatabaseNotifyManager { - private static final Uri BASE_URI = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY); + private static final Uri URI_KEYS = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY + "/keys"); + private static final Uri URI_APPS = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY + "/apps"); private ContentResolver contentResolver; @@ -42,11 +43,24 @@ public class DatabaseNotifyManager { contentResolver.notifyChange(uri, null); } + public void notifyApiAppChange(String apiApp) { + Uri uri = getNotifyUriPackageName(apiApp); + contentResolver.notifyChange(uri, null); + } + public static Uri getNotifyUriAllKeys() { - return BASE_URI; + return URI_KEYS; } public static Uri getNotifyUriMasterKeyId(long masterKeyId) { - return BASE_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); + return URI_KEYS.buildUpon().appendPath(Long.toString(masterKeyId)).build(); + } + + public static Uri getNotifyUriAllApps() { + return URI_APPS; + } + + public static Uri getNotifyUriPackageName(String packageName) { + return URI_APPS.buildUpon().appendPath(packageName).build(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/ApiAppsLiveData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/ApiAppsLiveData.java new file mode 100644 index 000000000..374b53dd6 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/livedata/ApiAppsLiveData.java @@ -0,0 +1,113 @@ +package org.sufficientlysecure.keychain.livedata; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager; +import org.sufficientlysecure.keychain.livedata.ApiAppsLiveData.ListedApp; +import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; + + +public class ApiAppsLiveData extends AsyncTaskLiveData> { + private final ApiAppDao apiAppDao; + private final PackageManager packageManager; + + public ApiAppsLiveData(Context context) { + super(context, DatabaseNotifyManager.getNotifyUriAllApps()); + + packageManager = getContext().getPackageManager(); + apiAppDao = ApiAppDao.getInstance(context); + } + + @Override + protected List asyncLoadData() { + ArrayList result = new ArrayList<>(); + + loadRegisteredApps(result); + addPlaceholderApps(result); + + Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName)); + return result; + } + + private void loadRegisteredApps(ArrayList result) { + List registeredApiApps = apiAppDao.getAllApiApps(); + + for (ApiApp apiApp : registeredApiApps) { + ListedApp listedApp; + try { + ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0); + CharSequence applicationLabel = packageManager.getApplicationLabel(ai); + Drawable applicationIcon = packageManager.getApplicationIcon(ai); + + listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null); + } catch (PackageManager.NameNotFoundException e) { + listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null); + } + result.add(listedApp); + } + } + + private void addPlaceholderApps(ArrayList result) { + for (ListedApp placeholderApp : PLACERHOLDER_APPS) { + if (!containsByPackageName(result, placeholderApp.packageName)) { + try { + packageManager.getApplicationInfo(placeholderApp.packageName, 0); + result.add(placeholderApp.withIsInstalled()); + } catch (PackageManager.NameNotFoundException e) { + result.add(placeholderApp); + } + } + } + } + + private boolean containsByPackageName(ArrayList result, String packageName) { + for (ListedApp app : result) { + if (packageName.equals(app.packageName)) { + return true; + } + } + return false; + } + + + public static class ListedApp { + public final String packageName; + public final boolean isInstalled; + public final boolean isRegistered; + public final String readableName; + public final Drawable applicationIcon; + public final Integer applicationIconRes; + + ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName, + Drawable applicationIcon, Integer applicationIconRes) { + this.packageName = packageName; + this.isInstalled = isInstalled; + this.isRegistered = isRegistered; + this.readableName = readableName.toString(); + this.applicationIcon = applicationIcon; + this.applicationIconRes = applicationIconRes; + } + + public ListedApp withIsInstalled() { + return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes); + } + } + + private static final ListedApp[] PLACERHOLDER_APPS = { + new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9), + new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store), + new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null, + R.drawable.apps_conversations) + }; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index 87aea3bd4..13406266e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -18,18 +18,18 @@ package org.sufficientlysecure.keychain.remote.ui; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.Adapter; @@ -40,11 +40,10 @@ import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.ApiApp; -import org.sufficientlysecure.keychain.daos.ApiAppDao; +import org.sufficientlysecure.keychain.livedata.ApiAppsLiveData; +import org.sufficientlysecure.keychain.livedata.ApiAppsLiveData.ListedApp; import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; -import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; import timber.log.Timber; @@ -61,7 +60,8 @@ public class AppsListFragment extends RecyclerFragment { setAdapter(adapter); setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); - new ApiAppsLiveData(getContext()).observe(this, this::onLoad); + ApiAppsViewModel viewModel = ViewModelProviders.of(this).get(ApiAppsViewModel.class); + viewModel.getListedAppLiveData(requireContext()).observe(this, this::onLoad); } private void onLoad(List apiApps) { @@ -85,7 +85,7 @@ public class AppsListFragment extends RecyclerFragment { startActivity(intent); } else { Intent i; - PackageManager manager = getActivity().getPackageManager(); + PackageManager manager = requireActivity().getPackageManager(); try { i = manager.getLaunchIntentForPackage(listedApp.packageName); if (i == null) { @@ -121,13 +121,14 @@ public class AppsListFragment extends RecyclerFragment { inflater = LayoutInflater.from(context); } + @NonNull @Override - public ApiAppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public ApiAppViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ApiAppViewHolder(inflater.inflate(R.layout.api_apps_adapter_list_item, parent, false)); } @Override - public void onBindViewHolder(ApiAppViewHolder holder, int position) { + public void onBindViewHolder(@NonNull ApiAppViewHolder holder, int position) { ListedApp item = data.get(position); holder.bind(item); } @@ -168,97 +169,15 @@ public class AppsListFragment extends RecyclerFragment { } } - public static class ApiAppsLiveData extends AsyncTaskLiveData> { - private final ApiAppDao apiAppDao; - private final PackageManager packageManager; + public static class ApiAppsViewModel extends ViewModel { + LiveData> listedAppLiveData; - ApiAppsLiveData(Context context) { - super(context, null); - - packageManager = getContext().getPackageManager(); - apiAppDao = ApiAppDao.getInstance(context); - } - - @Override - protected List asyncLoadData() { - ArrayList result = new ArrayList<>(); - - loadRegisteredApps(result); - addPlaceholderApps(result); - - Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName)); - return result; - } - - private void loadRegisteredApps(ArrayList result) { - List registeredApiApps = apiAppDao.getAllApiApps(); - - for (ApiApp apiApp : registeredApiApps) { - ListedApp listedApp; - try { - ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0); - CharSequence applicationLabel = packageManager.getApplicationLabel(ai); - Drawable applicationIcon = packageManager.getApplicationIcon(ai); - - listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null); - } catch (PackageManager.NameNotFoundException e) { - listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null); - } - result.add(listedApp); + LiveData> getListedAppLiveData(Context context) { + if (listedAppLiveData == null) { + listedAppLiveData = new ApiAppsLiveData(context); } - } - - private void addPlaceholderApps(ArrayList result) { - for (ListedApp placeholderApp : PLACERHOLDER_APPS) { - if (!containsByPackageName(result, placeholderApp.packageName)) { - try { - packageManager.getApplicationInfo(placeholderApp.packageName, 0); - result.add(placeholderApp.withIsInstalled()); - } catch (PackageManager.NameNotFoundException e) { - result.add(placeholderApp); - } - } - } - } - - private boolean containsByPackageName(ArrayList result, String packageName) { - for (ListedApp app : result) { - if (packageName.equals(app.packageName)) { - return true; - } - } - return false; + return listedAppLiveData; } } - public static class ListedApp { - final String packageName; - final boolean isInstalled; - final boolean isRegistered; - final String readableName; - final Drawable applicationIcon; - final Integer applicationIconRes; - - ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName, - Drawable applicationIcon, Integer applicationIconRes) { - this.packageName = packageName; - this.isInstalled = isInstalled; - this.isRegistered = isRegistered; - this.readableName = readableName.toString(); - this.applicationIcon = applicationIcon; - this.applicationIconRes = applicationIconRes; - } - - public ListedApp withIsInstalled() { - return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes); - } - } - - private static final ListedApp[] PLACERHOLDER_APPS = { - new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9), - new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store), - new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null, - R.drawable.apps_conversations) - }; - } From 71b4598687fb3b59815343983ec6df5f51a8b986 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 20:02:38 +0200 Subject: [PATCH 092/124] Refresh cancellation signal for newly launched operation --- .../sufficientlysecure/keychain/service/KeychainService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java index 2bd2b1b19..c3e9d9384 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -71,7 +71,7 @@ public class KeychainService extends Service implements Progressable { public static final String ACTION_CANCEL = "action_cancel"; // this attribute can possibly merged with the one above? not sure... - private CancellationSignal mActionCanceled = new CancellationSignal(); + private CancellationSignal mActionCanceled; ThreadLocal mMessenger = new ThreadLocal<>(); @@ -91,6 +91,8 @@ public class KeychainService extends Service implements Progressable { return START_NOT_STICKY; } + mActionCanceled = new CancellationSignal(); + Runnable actionRunnable = new Runnable() { @Override public void run() { From c98e835936b26c4e24ca20829b82c3c798de0687 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 20:04:49 +0200 Subject: [PATCH 093/124] drop dependency on SuperSlim --- OpenKeychain/build.gradle | 1 - .../keychain/ui/LogDisplayFragment.java | 4 ++-- .../keychain/ui/adapter/NestedLogAdapter.java | 16 ++++------------ 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index ae630da4f..94d9cffbf 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -35,7 +35,6 @@ dependencies { compile 'com.cocosw:bottomsheet:1.3.1@aar' // RecyclerView - compile 'com.tonicartos:superslim:0.4.13' compile 'eu.davidea:flexible-adapter:5.0.5' compile 'eu.davidea:flexible-adapter-ui:1.0.0-b5' compile 'eu.davidea:flexible-adapter-livedata:1.0.0-b2' diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 4092453c7..da0ebf658 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -25,11 +25,11 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import com.tonicartos.superslim.LayoutManager; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; @@ -79,7 +79,7 @@ public class LogDisplayFragment extends RecyclerFragment adapter.setListener(this); setAdapter(adapter); - setLayoutManager(new LayoutManager(getContext())); + setLayoutManager(new LinearLayoutManager(getContext())); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java index 756b19ae3..af50a1dbd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui.adapter; + +import java.util.ArrayList; +import java.util.List; + import android.content.Context; import android.graphics.Color; import android.support.v4.content.ContextCompat; @@ -29,15 +33,10 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.tonicartos.superslim.LayoutManager; - import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import java.util.ArrayList; -import java.util.List; - public class NestedLogAdapter extends RecyclerView.Adapter { private static final int ENTRY_TYPE_REGULAR = 0; private static final int ENTRY_TYPE_SUBLOG = 1; @@ -135,14 +134,7 @@ public class NestedLogAdapter extends RecyclerView.Adapter Date: Mon, 2 Jul 2018 20:06:47 +0200 Subject: [PATCH 094/124] include MaterialChipsInput as subdir lib --- OpenKeychain/build.gradle | 2 +- .../layout/encrypt_asymmetric_fragment.xml | 1 - extern/MaterialChipsInput/.gitignore | 8 + extern/MaterialChipsInput/build.gradle | 36 ++ extern/MaterialChipsInput/proguard-rules.pro | 25 + .../ExampleInstrumentedTest.java | 26 ++ .../src/main/AndroidManifest.xml | 4 + .../com/pchmn/materialchips/ChipView.java | 399 ++++++++++++++++ .../com/pchmn/materialchips/ChipsInput.java | 427 ++++++++++++++++++ .../materialchips/adapter/ChipsAdapter.java | 384 ++++++++++++++++ .../adapter/FilterableAdapter.java | 253 +++++++++++ .../com/pchmn/materialchips/model/Chip.java | 39 ++ .../materialchips/model/ChipInterface.java | 12 + .../materialchips/util/ActivityUtil.java | 20 + .../pchmn/materialchips/util/ColorUtil.java | 38 ++ .../util/LetterTileProvider.java | 221 +++++++++ .../materialchips/util/MyWindowCallback.java | 173 +++++++ .../pchmn/materialchips/util/ViewUtil.java | 81 ++++ .../views/ChipsInputEditText.java | 31 ++ .../materialchips/views/DetailedChipView.java | 233 ++++++++++ .../views/FilterableListView.java | 156 +++++++ .../views/ScrollViewMaxHeight.java | 49 ++ .../res/drawable-v21/ripple_chip_view.xml | 11 + .../src/main/res/drawable/bg_chip_view.xml | 10 + .../main/res/drawable/bg_chip_view_opened.xml | 10 + .../main/res/drawable/ic_cancel_grey_24dp.xml | 9 + .../res/drawable/ic_cancel_white_24dp.xml | 9 + .../drawable/ic_person_outline_white_24dp.xml | 9 + .../res/drawable/ic_person_white_24dp.xml | 9 + .../main/res/drawable/ripple_chip_view.xml | 10 + .../src/main/res/layout/chip_view.xml | 41 ++ .../src/main/res/layout/chips_input.xml | 17 + .../main/res/layout/detailed_chip_view.xml | 75 +++ .../main/res/layout/item_list_filterable.xml | 42 ++ .../main/res/layout/list_filterable_view.xml | 13 + .../src/main/res/values/attrs.xml | 34 ++ .../src/main/res/values/colors.xml | 10 + .../src/main/res/values/strings.xml | 20 + .../pchmn/materialchips/ExampleUnitTest.java | 17 + settings.gradle | 1 + 40 files changed, 2963 insertions(+), 2 deletions(-) create mode 100644 extern/MaterialChipsInput/.gitignore create mode 100644 extern/MaterialChipsInput/build.gradle create mode 100644 extern/MaterialChipsInput/proguard-rules.pro create mode 100644 extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java create mode 100644 extern/MaterialChipsInput/src/main/AndroidManifest.xml create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java create mode 100644 extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/chips_input.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/values/attrs.xml create mode 100644 extern/MaterialChipsInput/src/main/res/values/colors.xml create mode 100644 extern/MaterialChipsInput/src/main/res/values/strings.xml create mode 100644 extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 94d9cffbf..36d4afdb9 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -28,7 +28,6 @@ dependencies { // UI compile 'org.sufficientlysecure:html-textview:3.1' - compile 'com.github.sikeeoh:MaterialChipsInput:1.1.1' compile 'com.jpardogo.materialtabstrip:library:1.1.1' compile 'com.getbase:floatingactionbutton:1.10.1' compile 'com.nispok:snackbar:2.11.0' @@ -61,6 +60,7 @@ dependencies { implementation project(':extern:minidns') implementation project(':KeybaseLib') implementation project(':safeslinger-exchange') + implementation project(':extern:MaterialChipsInput') implementation "android.arch.work:work-runtime:1.0.0-alpha02" diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index b191c1b80..1ff410f43 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -46,7 +46,6 @@ android:paddingLeft="8dp" android:paddingRight="8dp" app:hint="@string/label_to" - app:chip_hasAvatarIcon="false" app:maxRows="2" app:chip_detailed_backgroundColor="@color/colorChipViewBackground" /> diff --git a/extern/MaterialChipsInput/.gitignore b/extern/MaterialChipsInput/.gitignore new file mode 100644 index 000000000..09b993d06 --- /dev/null +++ b/extern/MaterialChipsInput/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/extern/MaterialChipsInput/build.gradle b/extern/MaterialChipsInput/build.gradle new file mode 100644 index 000000000..d6a67320a --- /dev/null +++ b/extern/MaterialChipsInput/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 27 + buildToolsVersion "27.0.3" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 27 + versionCode 114 + versionName "1.1.4" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:27.1.0' + testCompile 'junit:junit:4.12' + + // recycler + compile 'com.android.support:recyclerview-v7:27.1.0' + compile 'com.beloo.widget:ChipsLayoutManager:0.3.7@aar' +} + diff --git a/extern/MaterialChipsInput/proguard-rules.pro b/extern/MaterialChipsInput/proguard-rules.pro new file mode 100644 index 000000000..0ae68584d --- /dev/null +++ b/extern/MaterialChipsInput/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/couleurwhatever/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java b/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java new file mode 100644 index 000000000..a28c3f326 --- /dev/null +++ b/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.pchmn.materialchips; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.pchmn.library.test", appContext.getPackageName()); + } +} diff --git a/extern/MaterialChipsInput/src/main/AndroidManifest.xml b/extern/MaterialChipsInput/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ce7e64f72 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java new file mode 100644 index 000000000..7b57353e2 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java @@ -0,0 +1,399 @@ +package com.pchmn.materialchips; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.support.annotation.ColorInt; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.LetterTileProvider; +import com.pchmn.materialchips.util.ViewUtil; + +public class ChipView extends RelativeLayout { + + private static final String TAG = ChipView.class.toString(); + // context + private Context mContext; + // xml elements + private LinearLayout mContentLayout; + private TextView mLabelTextView; + private ImageButton mDeleteButton; + // attributes + private static final int NONE = -1; + private String mLabel; + private ColorStateList mLabelColor; + private boolean mDeletable = false; + private Drawable mDeleteIcon; + private ColorStateList mDeleteIconColor; + private ColorStateList mBackgroundColor; + // letter tile provider + private LetterTileProvider mLetterTileProvider; + // chip + private ChipInterface mChip; + + public ChipView(Context context) { + super(context); + mContext = context; + init(null); + } + + public ChipView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(attrs); + } + + /** + * Inflate the view according to attributes + * + * @param attrs the attributes + */ + private void init(AttributeSet attrs) { + // inflate layout + View rootView = inflate(getContext(), R.layout.chip_view, this); + + mContentLayout = (LinearLayout) rootView.findViewById(R.id.content); + mLabelTextView = (TextView) rootView.findViewById(R.id.label); + mDeleteButton = (ImageButton) rootView.findViewById(R.id.delete_button); + + // letter tile provider + mLetterTileProvider = new LetterTileProvider(mContext); + + // attributes + if (attrs != null) { + TypedArray a = mContext.getTheme().obtainStyledAttributes( + attrs, + R.styleable.ChipView, + 0, 0); + + try { + // label + mLabel = a.getString(R.styleable.ChipView_label); + mLabelColor = a.getColorStateList(R.styleable.ChipView_labelColor); + mDeletable = a.getBoolean(R.styleable.ChipView_deletable, false); + mDeleteIconColor = a.getColorStateList(R.styleable.ChipView_deleteIconColor); + int deleteIconId = a.getResourceId(R.styleable.ChipView_deleteIcon, NONE); + if (deleteIconId != NONE) + mDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId); + // background color + mBackgroundColor = a.getColorStateList(R.styleable.ChipView_backgroundColor); + } finally { + a.recycle(); + } + } + + // inflate + inflateWithAttributes(); + } + + /** + * Inflate the view + */ + private void inflateWithAttributes() { + // label + setLabel(mLabel); + if (mLabelColor != null) + setLabelColor(mLabelColor); + + // delete button + setDeletable(mDeletable); + + // background color + if (mBackgroundColor != null) + setChipBackgroundColor(mBackgroundColor); + } + + public void inflate(ChipInterface chip) { + mChip = chip; + // label + mLabel = mChip.getLabel(); + // inflate + inflateWithAttributes(); + } + + /** + * Get label + * + * @return the label + */ + public String getLabel() { + return mLabel; + } + + /** + * Set label + * + * @param label the label to set + */ + public void setLabel(String label) { + mLabel = label; + mLabelTextView.setText(label); + } + + /** + * Set label color + * + * @param color the color to set + */ + public void setLabelColor(ColorStateList color) { + mLabelColor = color; + mLabelTextView.setTextColor(color); + } + + /** + * Set label color + * + * @param color the color to set + */ + public void setLabelColor(@ColorInt int color) { + mLabelColor = ColorStateList.valueOf(color); + mLabelTextView.setTextColor(color); + } + +// /** +// * Show or hide avatar icon +// * +// * @param hasAvatarIcon true to show, false to hide +// */ +// public void setHasAvatarIcon(boolean hasAvatarIcon) { +// mHasAvatarIcon = hasAvatarIcon; +// +// if(!mHasAvatarIcon) { +// // hide icon +// mAvatarIconImageView.setVisibility(GONE); +// // adjust padding +// if(mDeleteButton.getVisibility() == VISIBLE) +// mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0); +// else +// mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0); +// +// } +// else { +// // show icon +// mAvatarIconImageView.setVisibility(VISIBLE); +// // adjust padding +// if(mDeleteButton.getVisibility() == VISIBLE) +// mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0); +// else +// mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, ViewUtil.dpToPx(12), 0); +// +// // set icon +// if(mAvatarIconUri != null) +// mAvatarIconImageView.setImageURI(mAvatarIconUri); +// else if(mAvatarIconDrawable != null) +// mAvatarIconImageView.setImageDrawable(mAvatarIconDrawable); +// else +// mAvatarIconImageView.setImageBitmap(mLetterTileProvider.getLetterTile(getLabel())); +// } +// } + +// /** +// * Set avatar icon +// * +// * @param avatarIcon the icon to set +// */ +// public void setAvatarIcon(Drawable avatarIcon) { +// mAvatarIconDrawable = avatarIcon; +// mHasAvatarIcon = true; +// inflateWithAttributes(); +// } + +// /** +// * Set avatar icon +// * +// * @param avatarUri the uri of the icon to set +// */ +// public void setAvatarIcon(Uri avatarUri) { +// mAvatarIconUri = avatarUri; +// mHasAvatarIcon = true; +// inflateWithAttributes(); +// } + + /** + * Show or hide delte button + * + * @param deletable true to show, false to hide + */ + public void setDeletable(boolean deletable) { + mDeletable = deletable; + if (!mDeletable) { + // hide delete icon + mDeleteButton.setVisibility(GONE); + // adjust padding + mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0); + } else { + // show icon + mDeleteButton.setVisibility(VISIBLE); + // adjust padding + mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0); + + // set icon + if (mDeleteIcon != null) + mDeleteButton.setImageDrawable(mDeleteIcon); + if (mDeleteIconColor != null) + mDeleteButton.getDrawable().mutate().setColorFilter(mDeleteIconColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + } + } + + /** + * Set delete icon color + * + * @param color the color to set + */ + public void setDeleteIconColor(ColorStateList color) { + mDeleteIconColor = color; + mDeletable = true; + inflateWithAttributes(); + } + + /** + * Set delete icon color + * + * @param color the color to set + */ + public void setDeleteIconColor(@ColorInt int color) { + mDeleteIconColor = ColorStateList.valueOf(color); + mDeletable = true; + inflateWithAttributes(); + } + + /** + * Set delete icon + * + * @param deleteIcon the icon to set + */ + public void setDeleteIcon(Drawable deleteIcon) { + mDeleteIcon = deleteIcon; + mDeletable = true; + inflateWithAttributes(); + } + + /** + * Set background color + * + * @param color the color to set + */ + public void setChipBackgroundColor(ColorStateList color) { + mBackgroundColor = color; + setChipBackgroundColor(color.getDefaultColor()); + } + + /** + * Set background color + * + * @param color the color to set + */ + public void setChipBackgroundColor(@ColorInt int color) { + mBackgroundColor = ColorStateList.valueOf(color); + mContentLayout.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + + /** + * Set the chip object + * + * @param chip the chip + */ + public void setChip(ChipInterface chip) { + mChip = chip; + } + + /** + * Set OnClickListener on the delete button + * + * @param onClickListener the OnClickListener + */ + public void setOnDeleteClicked(OnClickListener onClickListener) { + mDeleteButton.setOnClickListener(onClickListener); + } + + /** + * Set OnclickListener on the entire chip + * + * @param onClickListener the OnClickListener + */ + public void setOnChipClicked(OnClickListener onClickListener) { + mContentLayout.setOnClickListener(onClickListener); + } + + /** + * Builder class + */ + public static class Builder { + private Context context; + private String label; + private ColorStateList labelColor; + private boolean deletable = false; + private Drawable deleteIcon; + private ColorStateList deleteIconColor; + private ColorStateList backgroundColor; + private ChipInterface chip; + + public Builder(Context context) { + this.context = context; + } + + public Builder label(String label) { + this.label = label; + return this; + } + + public Builder labelColor(ColorStateList labelColor) { + this.labelColor = labelColor; + return this; + } + + public Builder deletable(boolean deletable) { + this.deletable = deletable; + return this; + } + + public Builder deleteIcon(Drawable deleteIcon) { + this.deleteIcon = deleteIcon; + return this; + } + + public Builder deleteIconColor(ColorStateList deleteIconColor) { + this.deleteIconColor = deleteIconColor; + return this; + } + + public Builder backgroundColor(ColorStateList backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public Builder chip(ChipInterface chip) { + this.chip = chip; + this.label = chip.getLabel(); + return this; + } + + public ChipView build() { + return newInstance(this); + } + } + + private static ChipView newInstance(Builder builder) { + ChipView chipView = new ChipView(builder.context); + chipView.mLabel = builder.label; + chipView.mLabelColor = builder.labelColor; + chipView.mDeletable = builder.deletable; + chipView.mDeleteIcon = builder.deleteIcon; + chipView.mDeleteIconColor = builder.deleteIconColor; + chipView.mBackgroundColor = builder.backgroundColor; + chipView.mChip = builder.chip; + chipView.inflateWithAttributes(); + + return chipView; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java new file mode 100644 index 000000000..d1e7ad18d --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -0,0 +1,427 @@ +package com.pchmn.materialchips; + + +import android.app.Activity; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager; +import com.pchmn.materialchips.adapter.ChipsAdapter; +import com.pchmn.materialchips.model.Chip; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ActivityUtil; +import com.pchmn.materialchips.util.MyWindowCallback; +import com.pchmn.materialchips.util.ViewUtil; +import com.pchmn.materialchips.views.ChipsInputEditText; +import com.pchmn.materialchips.views.DetailedChipView; +import com.pchmn.materialchips.views.FilterableListView; +import com.pchmn.materialchips.views.ScrollViewMaxHeight; + +import java.util.ArrayList; +import java.util.List; + +public class ChipsInput extends ScrollViewMaxHeight { + + private static final String TAG = ChipsInput.class.toString(); + // context + private Context mContext; + // xml element + private RecyclerView mRecyclerView; + // adapter + private ChipsAdapter mChipsAdapter; + // attributes + private static final int NONE = -1; + private String mHint; + private ColorStateList mHintColor; + private ColorStateList mTextColor; + private int mMaxRows = 2; + private ColorStateList mChipLabelColor; + private boolean mChipDeletable = false; + private Drawable mChipDeleteIcon; + private ColorStateList mChipDeleteIconColor; + private ColorStateList mChipBackgroundColor; + private boolean mShowChipDetailed = true; + private ColorStateList mChipDetailedTextColor; + private ColorStateList mChipDetailedDeleteIconColor; + private ColorStateList mChipDetailedBackgroundColor; + private ColorStateList mFilterableListBackgroundColor; + private ColorStateList mFilterableListTextColor; + // chips listener + private List mChipsListenerList = new ArrayList<>(); + private ChipsListener mChipsListener; + // chip list + private List mFilterableChipList; + private FilterableListView mFilterableListView; + // chip validator + private ChipValidator mChipValidator; + private ViewGroup filterableListLayout; + private ChipsInputEditText mEditText; + + public ChipsInput(Context context) { + super(context); + mContext = context; + init(null); + } + + public ChipsInput(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(attrs); + } + + /** + * Inflate the view according to attributes + * + * @param attrs the attributes + */ + private void init(AttributeSet attrs) { + // inflate filterableListLayout + View rootView = inflate(getContext(), R.layout.chips_input, this); + + mRecyclerView = (RecyclerView) rootView.findViewById(R.id.chips_recycler); + + initEditText(); + + // attributes + if (attrs != null) { + TypedArray a = mContext.getTheme().obtainStyledAttributes( + attrs, + R.styleable.ChipsInput, + 0, 0); + + try { + // hint + mHint = a.getString(R.styleable.ChipsInput_hint); + mHintColor = a.getColorStateList(R.styleable.ChipsInput_hintColor); + mTextColor = a.getColorStateList(R.styleable.ChipsInput_textColor); + mMaxRows = a.getInteger(R.styleable.ChipsInput_maxRows, 2); + setMaxHeight(ViewUtil.dpToPx((40 * mMaxRows) + 8)); + //setVerticalScrollBarEnabled(true); + // chip label color + mChipLabelColor = a.getColorStateList(R.styleable.ChipsInput_chip_labelColor); + // chip delete icon + mChipDeletable = a.getBoolean(R.styleable.ChipsInput_chip_deletable, false); + mChipDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_deleteIconColor); + int deleteIconId = a.getResourceId(R.styleable.ChipsInput_chip_deleteIcon, NONE); + if (deleteIconId != NONE) + mChipDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId); + // chip background color + mChipBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_backgroundColor); + // show chip detailed + mShowChipDetailed = a.getBoolean(R.styleable.ChipsInput_showChipDetailed, true); + // chip detailed text color + mChipDetailedTextColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_textColor); + mChipDetailedBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_backgroundColor); + mChipDetailedDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_deleteIconColor); + // filterable list + mFilterableListBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_backgroundColor); + mFilterableListTextColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_textColor); + } finally { + a.recycle(); + } + } + + // adapter + mChipsAdapter = new ChipsAdapter(mContext, this, mEditText, mRecyclerView); + ChipsLayoutManager chipsLayoutManager = ChipsLayoutManager.newBuilder(mContext) + .setOrientation(ChipsLayoutManager.HORIZONTAL) + .build(); + mRecyclerView.setLayoutManager(chipsLayoutManager); + mRecyclerView.setNestedScrollingEnabled(false); + mRecyclerView.setAdapter(mChipsAdapter); + + // set window callback + // will hide DetailedOpenView and hide keyboard on touch outside + Activity activity = ActivityUtil.scanForActivity(mContext); + if (activity == null) + throw new ClassCastException("android.view.Context cannot be cast to android.app.Activity"); + + android.view.Window.Callback mCallBack = (activity).getWindow().getCallback(); + activity.getWindow().setCallback(new MyWindowCallback(mCallBack, activity)); + } + + private void initEditText() { + mEditText = new ChipsInputEditText(mContext); + if (mHintColor != null) + mEditText.setHintTextColor(mHintColor); + if (mTextColor != null) + mEditText.setTextColor(mTextColor); + + mEditText.setLayoutParams(new RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + mEditText.setHint(mHint); + mEditText.setBackgroundResource(android.R.color.transparent); + // prevent fullscreen on landscape + mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); + mEditText.setPrivateImeOptions("nm"); + // no suggestion + mEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + + // handle back space + mEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + // backspace + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + // remove last chip + if (mEditText.getText().toString().length() == 0) + mChipsAdapter.removeLastChip(); + } + return false; + } + }); + + mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((actionId == EditorInfo.IME_ACTION_DONE) || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { + ChipsInput.this.onActionDone(mEditText.getText().toString()); + } + return false; + } + }); + + // text changed + mEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ChipsInput.this.onTextChanged(s); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + public void addChips(List chipList) { + mChipsAdapter.addChipsProgrammatically(chipList); + } + + public void addChip(ChipInterface chip) { + mChipsAdapter.addChip(chip); + } + + public void addChip(Object id, String label, String info) { + Chip chip = new Chip(id, label, info); + mChipsAdapter.addChip(chip); + } + + public void addChip(String label, String info) { + Chip chip = new Chip(label, info); + mChipsAdapter.addChip(chip); + } + + public void removeChip(ChipInterface chip) { + mChipsAdapter.removeChip(chip); + } + + public void removeChipById(Object id) { + mChipsAdapter.removeChipById(id); + } + + public void removeChipByLabel(String label) { + mChipsAdapter.removeChipByLabel(label); + } + + public void removeChipByInfo(String info) { + mChipsAdapter.removeChipByInfo(info); + } + + public ChipView getChipView() { + int padding = ViewUtil.dpToPx(4); + ChipView chipView = new ChipView.Builder(mContext) + .labelColor(mChipLabelColor) + .deletable(mChipDeletable) + .deleteIcon(mChipDeleteIcon) + .deleteIconColor(mChipDeleteIconColor) + .backgroundColor(mChipBackgroundColor) + .build(); + + chipView.setPadding(padding, padding, padding, padding); + + return chipView; + } + + public ChipsInputEditText getEditText() { + return mChipsAdapter.getmEditText(); + } + + public DetailedChipView getDetailedChipView(ChipInterface chip) { + return new DetailedChipView.Builder(mContext) + .chip(chip) + .textColor(mChipDetailedTextColor) + .backgroundColor(mChipDetailedBackgroundColor) + .deleteIconColor(mChipDetailedDeleteIconColor) + .build(); + } + + public void addChipsListener(ChipsListener chipsListener) { + mChipsListenerList.add(chipsListener); + mChipsListener = chipsListener; + } + + public void onChipAdded(ChipInterface chip, int size) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onChipAdded(chip, size); + } + } + + public void onChipRemoved(ChipInterface chip, int size) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onChipRemoved(chip, size); + } + } + + public void onTextChanged(CharSequence text) { + if (mChipsListener != null) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onTextChanged(text); + } + // show filterable list + if (mFilterableListView != null) { + if (text.length() > 0) + mFilterableListView.filterList(text); + else + mFilterableListView.fadeOut(); + } + } + } + + public void onActionDone(CharSequence text) { + if (mChipsListener != null) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onActionDone(text); + } + } + } + + public List getSelectedChipList() { + return mChipsAdapter.getChipList(); + } + + public String getHint() { + return mHint; + } + + public void setHint(String mHint) { + this.mHint = mHint; + } + + public void setHintColor(ColorStateList mHintColor) { + this.mHintColor = mHintColor; + } + + public void setTextColor(ColorStateList mTextColor) { + this.mTextColor = mTextColor; + } + + public ChipsInput setMaxRows(int mMaxRows) { + this.mMaxRows = mMaxRows; + return this; + } + + public void setChipLabelColor(ColorStateList mLabelColor) { + this.mChipLabelColor = mLabelColor; + } + + public void setChipDeletable(boolean mDeletable) { + this.mChipDeletable = mDeletable; + } + + public void setChipDeleteIcon(Drawable mDeleteIcon) { + this.mChipDeleteIcon = mDeleteIcon; + } + + public void setChipDeleteIconColor(ColorStateList mDeleteIconColor) { + this.mChipDeleteIconColor = mDeleteIconColor; + } + + public void setChipBackgroundColor(ColorStateList mBackgroundColor) { + this.mChipBackgroundColor = mBackgroundColor; + } + + public ChipsInput setShowChipDetailed(boolean mShowChipDetailed) { + this.mShowChipDetailed = mShowChipDetailed; + return this; + } + + public boolean isShowChipDetailed() { + return mShowChipDetailed; + } + + public void setChipDetailedTextColor(ColorStateList mChipDetailedTextColor) { + this.mChipDetailedTextColor = mChipDetailedTextColor; + } + + public void setChipDetailedDeleteIconColor(ColorStateList mChipDetailedDeleteIconColor) { + this.mChipDetailedDeleteIconColor = mChipDetailedDeleteIconColor; + } + + public void setChipDetailedBackgroundColor(ColorStateList mChipDetailedBackgroundColor) { + this.mChipDetailedBackgroundColor = mChipDetailedBackgroundColor; + } + + public void setFilterableListLayout(ViewGroup layout) { + this.filterableListLayout = layout; + } + + public void setFilterableList(List list) { + mFilterableChipList = list; + if (filterableListLayout != null) { + mFilterableListView = new FilterableListView(mContext, filterableListLayout); + } else { + mFilterableListView = new FilterableListView(mContext); + } + mFilterableListView.build(mFilterableChipList, this, mFilterableListBackgroundColor, mFilterableListTextColor); + mChipsAdapter.setFilterableListView(mFilterableListView); + } + + public List getFilterableList() { + return mFilterableChipList; + } + + public ChipValidator getChipValidator() { + return mChipValidator; + } + + public void setChipValidator(ChipValidator mChipValidator) { + this.mChipValidator = mChipValidator; + } + + public interface ChipsListener { + void onChipAdded(ChipInterface chip, int newSize); + + void onChipRemoved(ChipInterface chip, int newSize); + + void onTextChanged(CharSequence text); + + void onActionDone(CharSequence text); + } + + public interface ChipValidator { + boolean areEquals(ChipInterface chip1, ChipInterface chip2); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java new file mode 100644 index 000000000..64d8216a1 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -0,0 +1,384 @@ +package com.pchmn.materialchips.adapter; + +import android.content.Context; +import android.os.Build; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.EditText; +import android.widget.RelativeLayout; + +import com.pchmn.materialchips.ChipView; +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ViewUtil; +import com.pchmn.materialchips.views.ChipsInputEditText; +import com.pchmn.materialchips.views.DetailedChipView; +import com.pchmn.materialchips.views.FilterableListView; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +public class ChipsAdapter extends RecyclerView.Adapter { + + private static final int TYPE_EDIT_TEXT = 0; + private static final int TYPE_ITEM = 1; + private Context mContext; + private ChipsInput mChipsInput; + private List mChipList = new ArrayList<>(); + private String mHintLabel; + + private ChipsInputEditText mEditText; + + private RecyclerView mRecycler; + + public ChipsAdapter(Context context, ChipsInput chipsInput, RecyclerView recycler) { + mContext = context; + mChipsInput = chipsInput; + mRecycler = recycler; + mHintLabel = mChipsInput.getHint(); + } + + public ChipsAdapter(Context mContext, ChipsInput chipsInput, ChipsInputEditText mEditText, RecyclerView mRecyclerView) { + this(mContext, chipsInput, mRecyclerView); + this.mEditText = mEditText; + } + + private class ItemViewHolder extends RecyclerView.ViewHolder { + + private final ChipView chipView; + + ItemViewHolder(View view) { + super(view); + chipView = (ChipView) view; + } + + } + private class EditTextViewHolder extends RecyclerView.ViewHolder { + + private final EditText editText; + + EditTextViewHolder(View view) { + super(view); + editText = (EditText) view; + } + + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == TYPE_EDIT_TEXT) { + return new EditTextViewHolder(mEditText); + } else { + return new ItemViewHolder(mChipsInput.getChipView()); + } + + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + // edit text + if (position == mChipList.size()) { + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + + // auto fit edit text + autofitEditText(); + } + // chip + else if (getItemCount() > 1) { + ItemViewHolder itemViewHolder = (ItemViewHolder) holder; + itemViewHolder.chipView.inflate(getItem(position)); + // handle click + handleClickOnEditText(itemViewHolder.chipView, position); + } + } + + @Override + public int getItemCount() { + return mChipList.size() + 1; + } + + private ChipInterface getItem(int position) { + return mChipList.get(position); + } + + @Override + public int getItemViewType(int position) { + if (position == mChipList.size()) { + return TYPE_EDIT_TEXT; + } + + return TYPE_ITEM; + } + + @Override + public long getItemId(int position) { + return mChipList.get(position).hashCode(); + } + + private void autofitEditText() { + // min width of edit text = 50 dp + ViewGroup.LayoutParams params = mEditText.getLayoutParams(); + params.width = ViewUtil.dpToPx(50); + mEditText.setLayoutParams(params); + + // listen to change in the tree + mEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + + @Override + public void onGlobalLayout() { + // get right of recycler and left of edit text + int right = mRecycler.getRight(); + int left = mEditText.getLeft(); + + // edit text will fill the space + ViewGroup.LayoutParams params = mEditText.getLayoutParams(); + params.width = right - left - ViewUtil.dpToPx(8); + mEditText.setLayoutParams(params); + + // request focus + mEditText.requestFocus(); + + // remove the listener: + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mEditText.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mEditText.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + } + + }); + } + + private void handleClickOnEditText(ChipView chipView, final int position) { + // delete chip + chipView.setOnDeleteClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeChip(position); + } + }); + + // show detailed chip + if (mChipsInput.isShowChipDetailed()) { + chipView.setOnChipClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + // get chip position + int[] coord = new int[2]; + v.getLocationInWindow(coord); + + final DetailedChipView detailedChipView = mChipsInput.getDetailedChipView(getItem(position)); + setDetailedChipViewPosition(detailedChipView, coord); + + // delete button + detailedChipView.setOnDeleteClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeChip(position); + detailedChipView.fadeOut(); + } + }); + } + }); + } + } + + private void setDetailedChipViewPosition(DetailedChipView detailedChipView, int[] coord) { + // window width + ViewGroup rootView = (ViewGroup) mRecycler.getRootView(); + int windowWidth = ViewUtil.getWindowWidth(mContext); + + // chip size + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + ViewUtil.dpToPx(300), + ViewUtil.dpToPx(100)); + + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + // align left window + if (coord[0] <= 0) { + layoutParams.leftMargin = 0; + layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13); + detailedChipView.alignLeft(); + } + // align right + else if (coord[0] + ViewUtil.dpToPx(300) > windowWidth + ViewUtil.dpToPx(13)) { + layoutParams.leftMargin = windowWidth - ViewUtil.dpToPx(300); + layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13); + detailedChipView.alignRight(); + } + // same position as chip + else { + layoutParams.leftMargin = coord[0] - ViewUtil.dpToPx(13); + layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13); + } + + // show view + rootView.addView(detailedChipView, layoutParams); + detailedChipView.fadeIn(); + } + + public void setFilterableListView(FilterableListView filterableListView) { + if (mEditText != null) { + mEditText.setFilterableListView(filterableListView); + } + } + + public void addChipsProgrammatically(List chipList) { + if (chipList != null) { + if (chipList.size() > 0) { + int chipsBeforeAdding = getItemCount(); + for (ChipInterface chip : chipList) { + mChipList.add(chip); + mChipsInput.onChipAdded(chip, getItemCount()); + } + + // hide hint + mEditText.setHint(null); + // reset text + mEditText.setText(null); + + notifyItemRangeChanged(chipsBeforeAdding, chipList.size()); + } + } + } + + public void addChip(ChipInterface chip) { + if (!listContains(mChipList, chip)) { + mChipList.add(chip); + // notify listener + mChipsInput.onChipAdded(chip, mChipList.size()); + // hide hint + mEditText.setHint(null); + // reset text + mEditText.setText(null); + // refresh data + notifyItemInserted(mChipList.size()); + } + } + + public void removeChip(ChipInterface chip) { + int position = mChipList.indexOf(chip); + mChipList.remove(position); + // notify listener + notifyItemRangeChanged(position, getItemCount()); + mChipsInput.onChipRemoved(chip, mChipList.size()); + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChip(int position) { + ChipInterface chip = mChipList.get(position); + // remove contact + mChipList.remove(position); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChipById(Object id) { + for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { + ChipInterface chip = iter.next(); + if (chip.getId() != null && chip.getId().equals(id)) { + // remove chip + iter.remove(); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + } + } + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChipByLabel(String label) { + for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { + ChipInterface chip = iter.next(); + if (chip.getLabel().equals(label)) { + // remove chip + iter.remove(); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + } + } + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChipByInfo(String info) { + for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { + ChipInterface chip = iter.next(); + if (chip.getInfo() != null && chip.getInfo().equals(info)) { + // remove chip + iter.remove(); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + } + } + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeLastChip() { + if (mChipList.size() > 0) { + removeChip(mChipList.get(mChipList.size() - 1)); + } + } + + public List getChipList() { + return mChipList; + } + + private boolean listContains(List contactList, ChipInterface chip) { + + if (mChipsInput.getChipValidator() != null) { + for (ChipInterface item : contactList) { + if (mChipsInput.getChipValidator().areEquals(item, chip)) { + return true; + } + } + } else { + for (ChipInterface item : contactList) { + if (chip.getId() != null && chip.getId().equals(item.getId())) { + return true; + } + if (chip.getLabel().equals(item.getLabel())) { + return true; + } + } + } + + return false; + } + + public ChipsInputEditText getmEditText() { + return mEditText; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java new file mode 100644 index 000000000..2895a4252 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java @@ -0,0 +1,253 @@ +package com.pchmn.materialchips.adapter; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.TextView; + +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ColorUtil; +import com.pchmn.materialchips.util.LetterTileProvider; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import static android.view.View.GONE; + +public class FilterableAdapter extends RecyclerView.Adapter implements Filterable { + + private static final String TAG = FilterableAdapter.class.toString(); + // context + private Context mContext; + // list + private List mOriginalList = new ArrayList<>(); + private List mChipList = new ArrayList<>(); + private List mFilteredList = new ArrayList<>(); + private ChipFilter mFilter; + private ChipsInput mChipsInput; + private LetterTileProvider mLetterTileProvider; + private ColorStateList mBackgroundColor; + private ColorStateList mTextColor; + // recycler + private RecyclerView mRecyclerView; + // sort + private Comparator mComparator; + private Collator mCollator; + + + public FilterableAdapter(Context context, + RecyclerView recyclerView, + List chipList, + ChipsInput chipsInput, + ColorStateList backgroundColor, + ColorStateList textColor) { + mContext = context; + mRecyclerView = recyclerView; + mCollator = Collator.getInstance(Locale.getDefault()); + mCollator.setStrength(Collator.PRIMARY); + mComparator = new Comparator() { + @Override + public int compare(ChipInterface o1, ChipInterface o2) { + return mCollator.compare(o1.getLabel(), o2.getLabel()); + } + }; + // remove chips that do not have label + Iterator iterator = chipList.iterator(); + while (iterator.hasNext()) { + if (iterator.next().getLabel() == null) + iterator.remove(); + } + sortList(chipList); + mOriginalList.addAll(chipList); + mChipList.addAll(chipList); + mFilteredList.addAll(chipList); + mLetterTileProvider = new LetterTileProvider(mContext); + mBackgroundColor = backgroundColor; + mTextColor = textColor; + mChipsInput = chipsInput; + + mChipsInput.addChipsListener(new ChipsInput.ChipsListener() { + @Override + public void onChipAdded(ChipInterface chip, int newSize) { + removeChip(chip); + } + + @Override + public void onChipRemoved(ChipInterface chip, int newSize) { + addChip(chip); + } + + @Override + public void onTextChanged(CharSequence text) { + mRecyclerView.scrollToPosition(0); + } + + @Override + public void onActionDone(CharSequence text) { + mRecyclerView.scrollToPosition(0); + } + }); + } + + private class ItemViewHolder extends RecyclerView.ViewHolder { + + private TextView mLabel; + private TextView mInfo; + + ItemViewHolder(View view) { + super(view); + mLabel = (TextView) view.findViewById(R.id.label); + mInfo = (TextView) view.findViewById(R.id.info); + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(mContext).inflate(R.layout.item_list_filterable, parent, false); + return new ItemViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ItemViewHolder itemViewHolder = (ItemViewHolder) holder; + final ChipInterface chip = getItem(position); + + // label + itemViewHolder.mLabel.setText(chip.getLabel()); + + // info + if (chip.getInfo() != null) { + itemViewHolder.mInfo.setVisibility(View.VISIBLE); + itemViewHolder.mInfo.setText(chip.getInfo()); + } else { + itemViewHolder.mInfo.setVisibility(GONE); + } + + // colors + if (mBackgroundColor != null) + itemViewHolder.itemView.getBackground().setColorFilter(mBackgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + if (mTextColor != null) { + itemViewHolder.mLabel.setTextColor(mTextColor); + itemViewHolder.mInfo.setTextColor(ColorUtil.alpha(mTextColor.getDefaultColor(), 150)); + } + + // onclick + itemViewHolder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mChipsInput != null) + mChipsInput.addChip(chip); + } + }); + } + + @Override + public int getItemCount() { + return mFilteredList.size(); + } + + private ChipInterface getItem(int position) { + return mFilteredList.get(position); + } + + @Override + public Filter getFilter() { + if (mFilter == null) + mFilter = new ChipFilter(this, mChipList); + return mFilter; + } + + private class ChipFilter extends Filter { + + private FilterableAdapter adapter; + private List originalList; + private List filteredList; + + public ChipFilter(FilterableAdapter adapter, List originalList) { + super(); + this.adapter = adapter; + this.originalList = originalList; + this.filteredList = new ArrayList<>(); + } + + @Override + protected FilterResults performFiltering(CharSequence constraint) { + filteredList.clear(); + FilterResults results = new FilterResults(); + if (constraint.length() == 0) { + filteredList.addAll(originalList); + } else { + final String filterPattern = constraint.toString().toLowerCase().trim(); + for (ChipInterface chip : originalList) { + if (chip.getLabel().toLowerCase().contains(filterPattern)) { + filteredList.add(chip); + } else if (chip.getInfo() != null && chip.getInfo().toLowerCase().replaceAll("\\s", "").contains(filterPattern)) { + filteredList.add(chip); + } + } + } + + results.values = filteredList; + results.count = filteredList.size(); + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mFilteredList.clear(); + mFilteredList.addAll((ArrayList) results.values); + notifyDataSetChanged(); + } + } + + private void removeChip(ChipInterface chip) { + int position = mFilteredList.indexOf(chip); + if (position >= 0) + mFilteredList.remove(position); + + position = mChipList.indexOf(chip); + if (position >= 0) + mChipList.remove(position); + + notifyDataSetChanged(); + } + + private void addChip(ChipInterface chip) { + if (contains(chip)) { + mChipList.add(chip); + mFilteredList.add(chip); + // sort original list + sortList(mChipList); + // sort filtered list + sortList(mFilteredList); + + notifyDataSetChanged(); + } + } + + private boolean contains(ChipInterface chip) { + for (ChipInterface item : mOriginalList) { + if (item.equals(chip)) + return true; + } + return false; + } + + private void sortList(List list) { + Collections.sort(list, mComparator); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java new file mode 100644 index 000000000..e79c79cd9 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java @@ -0,0 +1,39 @@ +package com.pchmn.materialchips.model; + + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class Chip implements ChipInterface { + + private Object id; + private String label; + private String info; + + public Chip(@NonNull Object id, @NonNull String label, @Nullable String info) { + this.id = id; + this.label = label; + this.info = info; + } + + + public Chip(@NonNull String label, @Nullable String info) { + this.label = label; + this.info = info; + } + + @Override + public Object getId() { + return id; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public String getInfo() { + return info; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java new file mode 100644 index 000000000..804ef47f1 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java @@ -0,0 +1,12 @@ +package com.pchmn.materialchips.model; + + +import android.graphics.drawable.Drawable; +import android.net.Uri; + +public interface ChipInterface { + + Object getId(); + String getLabel(); + String getInfo(); +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java new file mode 100644 index 000000000..c119fad07 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java @@ -0,0 +1,20 @@ +package com.pchmn.materialchips.util; + + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; + +public class ActivityUtil { + + public static Activity scanForActivity(Context context) { + if (context == null) + return null; + else if (context instanceof Activity) + return (Activity)context; + else if (context instanceof ContextWrapper) + return scanForActivity(((ContextWrapper)context).getBaseContext()); + + return null; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java new file mode 100644 index 000000000..c1b118c1c --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java @@ -0,0 +1,38 @@ +package com.pchmn.materialchips.util; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.util.TypedValue; + +import com.pchmn.materialchips.R; + +public class ColorUtil { + + public static int lighter(int color, float factor) { + int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255); + int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255); + int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255); + return Color.argb(Color.alpha(color), red, green, blue); + } + + public static int lighter(ColorStateList color, float factor) { + return lighter(color.getDefaultColor(), factor); + } + + public static int alpha(int color, int alpha) { + return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + public static boolean isColorDark(int color){ + double darkness = 1 - (0.2126*Color.red(color) + 0.7152*Color.green(color) + 0.0722*Color.blue(color))/255; + return darkness >= 0.5; + } + + public static int getThemeAccentColor (final Context context) { + final TypedValue value = new TypedValue (); + context.getTheme ().resolveAttribute (R.attr.colorAccent, value, true); + return value.data; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java new file mode 100644 index 000000000..9028a5286 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java @@ -0,0 +1,221 @@ +package com.pchmn.materialchips.util; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.text.TextPaint; +import android.util.Log; + +import com.pchmn.materialchips.R; + +/** + * Used to create a {@link Bitmap} that contains a letter used in the English + * alphabet or digit, if there is no letter or digit available, a default image + * is shown instead + */ +public class LetterTileProvider { + + /** The number of available tile colors (see R.array.letter_tile_colors) */ + private static final int NUM_OF_TILE_COLORS = 8; + + /** The {@link TextPaint} used to draw the letter onto the tile */ + private final TextPaint mPaint = new TextPaint(); + /** The bounds that enclose the letter */ + private final Rect mBounds = new Rect(); + /** The {@link Canvas} to draw on */ + private final Canvas mCanvas = new Canvas(); + /** The first char of the name being displayed */ + private final char[] mFirstChar = new char[1]; + + /** The background colors of the tile */ + private final TypedArray mColors; + /** The font size used to display the letter */ + private final int mTileLetterFontSize; + /** The default image to display */ + private final Bitmap mDefaultBitmap; + + /** Width */ + private final int mWidth; + /** Height */ + private final int mHeight; + + /** + * Constructor for LetterTileProvider + * + * @param context The {@link Context} to use + */ + public LetterTileProvider(Context context) { + final Resources res = context.getResources(); + + mPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + mPaint.setColor(Color.WHITE); + mPaint.setTextAlign(Paint.Align.CENTER); + mPaint.setAntiAlias(true); + + mColors = res.obtainTypedArray(R.array.letter_tile_colors); + mTileLetterFontSize = res.getDimensionPixelSize(R.dimen.tile_letter_font_size); + + //mDefaultBitmap = BitmapFactory.decodeResource(res, android.R.drawable.); + mDefaultBitmap = drawableToBitmap(ContextCompat.getDrawable(context, R.drawable.ic_person_white_24dp)); + mWidth = res.getDimensionPixelSize(R.dimen.letter_tile_size); + mHeight = res.getDimensionPixelSize(R.dimen.letter_tile_size); + } + + /** + * @param displayName The name used to create the letter for the tile + * @return A {@link Bitmap} that contains a letter used in the English + * alphabet or digit, if there is no letter or digit available, a + * default image is shown instead + */ + public Bitmap getLetterTile(String displayName) { + // workaround + if(displayName == null || displayName.length() == 0) + return null; + + final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); + + final char firstChar = displayName.charAt(0); + + final Canvas c = mCanvas; + c.setBitmap(bitmap); + c.drawColor(pickColor(displayName)); + + if (isLetterOrDigit(firstChar)) { + mFirstChar[0] = Character.toUpperCase(firstChar); + mPaint.setTextSize(mTileLetterFontSize); + mPaint.getTextBounds(mFirstChar, 0, 1, mBounds); + c.drawText(mFirstChar, 0, 1, mWidth / 2, mHeight / 2 + + (mBounds.bottom - mBounds.top) / 2, mPaint); + } + else { + // (32 - 24) / 2 = 4 + c.drawBitmap(mDefaultBitmap, ViewUtil.dpToPx(4), ViewUtil.dpToPx(4), null); + } + return bitmap; + } + + /** + * @param displayName The name used to create the letter for the tile + * @return A circular {@link Bitmap} that contains a letter used in the English + * alphabet or digit, if there is no letter or digit available, a + * default image is shown instead + */ + public Bitmap getCircularLetterTile(String displayName) { + // workaround + if(displayName == null || displayName.length() == 0) + displayName = "."; + + final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); + final char firstChar = displayName.charAt(0); + + final Canvas c = mCanvas; + c.setBitmap(bitmap); + c.drawColor(pickColor(displayName)); + + if (isLetterOrDigit(firstChar)) { + mFirstChar[0] = Character.toUpperCase(firstChar); + mPaint.setTextSize(mTileLetterFontSize); + mPaint.getTextBounds(mFirstChar, 0, 1, mBounds); + c.drawText(mFirstChar, 0, 1, mWidth / 2, mHeight / 2 + + (mBounds.bottom - mBounds.top) / 2, mPaint); + } else { + // (32 - 24) / 2 = 4 + c.drawBitmap(mDefaultBitmap, ViewUtil.dpToPx(4), ViewUtil.dpToPx(4), null); + } + return getCircularBitmap(bitmap); + } + + /** + * @param c The char to check + * @return True if c is in the English alphabet or is a digit, + * false otherwise + */ + private static boolean isLetterOrDigit(char c) { + //return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9'; + return Character.isLetterOrDigit(c); + } + + /** + * @param key The key used to generate the tile color + * @return A new or previously chosen color for key used as the + * tile background color + */ + private int pickColor(String key) { + // String.hashCode() is not supposed to change across java versions, so + // this should guarantee the same key always maps to the same color + final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS; + try { + return mColors.getColor(color, Color.BLACK); + } finally { + // bug with recycler view + //mColors.recycle(); + } + } + + private Bitmap getCircularBitmap(Bitmap bitmap) { + Bitmap output; + + if (bitmap.getWidth() > bitmap.getHeight()) { + output = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + } else { + output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getWidth(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + + float r = 0; + + if (bitmap.getWidth() > bitmap.getHeight()) { + r = bitmap.getHeight() / 2; + } else { + r = bitmap.getWidth() / 2; + } + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawCircle(r, r, r, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + return output; + } + + public static Bitmap drawableToBitmap (Drawable drawable) { + Bitmap bitmap = null; + + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if(bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } + + if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java new file mode 100644 index 000000000..924350801 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java @@ -0,0 +1,173 @@ +package com.pchmn.materialchips.util; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.view.ActionMode; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import com.pchmn.materialchips.views.ChipsInputEditText; +import com.pchmn.materialchips.views.DetailedChipView; + +public class MyWindowCallback implements Window.Callback { + + private Window.Callback mLocalCallback; + private Activity mActivity; + + public MyWindowCallback(Window.Callback localCallback, Activity activity) { + mLocalCallback = localCallback; + mActivity = activity; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent keyEvent) { + return mLocalCallback.dispatchKeyEvent(keyEvent); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) { + return mLocalCallback.dispatchKeyShortcutEvent(keyEvent); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent motionEvent) { + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + View v = mActivity.getCurrentFocus(); + if(v instanceof DetailedChipView) { + Rect outRect = new Rect(); + v.getGlobalVisibleRect(outRect); + if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { + ((DetailedChipView) v).fadeOut(); + } + } + if (v instanceof ChipsInputEditText) { + Rect outRect = new Rect(); + v.getGlobalVisibleRect(outRect); + if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY()) + && !((ChipsInputEditText) v).isFilterableListVisible()) { + InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + } + } + return mLocalCallback.dispatchTouchEvent(motionEvent); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent motionEvent) { + return mLocalCallback.dispatchTrackballEvent(motionEvent); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) { + return mLocalCallback.dispatchGenericMotionEvent(motionEvent); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + return mLocalCallback.dispatchPopulateAccessibilityEvent(accessibilityEvent); + } + + @Nullable + @Override + public View onCreatePanelView(int i) { + return mLocalCallback.onCreatePanelView(i); + } + + @Override + public boolean onCreatePanelMenu(int i, Menu menu) { + return mLocalCallback.onCreatePanelMenu(i, menu); + } + + @Override + public boolean onPreparePanel(int i, View view, Menu menu) { + return mLocalCallback.onPreparePanel(i, view, menu); + } + + @Override + public boolean onMenuOpened(int i, Menu menu) { + return mLocalCallback.onMenuOpened(i, menu); + } + + @Override + public boolean onMenuItemSelected(int i, MenuItem menuItem) { + return mLocalCallback.onMenuItemSelected(i, menuItem); + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) { + mLocalCallback.onWindowAttributesChanged(layoutParams); + } + + @Override + public void onContentChanged() { + mLocalCallback.onContentChanged(); + } + + @Override + public void onWindowFocusChanged(boolean b) { + mLocalCallback.onWindowFocusChanged(b); + } + + @Override + public void onAttachedToWindow() { + mLocalCallback.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + mLocalCallback.onDetachedFromWindow(); + } + + @Override + public void onPanelClosed(int i, Menu menu) { + mLocalCallback.onPanelClosed(i, menu); + } + + @Override + public boolean onSearchRequested() { + return mLocalCallback.onSearchRequested(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return mLocalCallback.onSearchRequested(searchEvent); + } + + @Nullable + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return mLocalCallback.onWindowStartingActionMode(callback); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Nullable + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) { + return mLocalCallback.onWindowStartingActionMode(callback, i); + } + + @Override + public void onActionModeStarted(ActionMode actionMode) { + mLocalCallback.onActionModeStarted(actionMode); + } + + @Override + public void onActionModeFinished(ActionMode actionMode) { + mLocalCallback.onActionModeFinished(actionMode); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java new file mode 100644 index 000000000..1cae87da7 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java @@ -0,0 +1,81 @@ +package com.pchmn.materialchips.util; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +public class ViewUtil { + + private static int windowWidthPortrait = 0; + private static int windowWidthLandscape = 0; + + public static int dpToPx(int dp) { + return (int) (dp * Resources.getSystem().getDisplayMetrics().density); + } + + public static int pxToDp(int px) { + return (int) (px / Resources.getSystem().getDisplayMetrics().density); + } + + public static int getWindowWidth(Context context) { + if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){ + return getWindowWidthPortrait(context); + } + else { + return getWindowWidthLandscape(context); + } + } + + private static int getWindowWidthPortrait(Context context) { + if(windowWidthPortrait == 0) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + windowWidthPortrait = metrics.widthPixels; + } + + return windowWidthPortrait; + } + + private static int getWindowWidthLandscape(Context context) { + if(windowWidthLandscape == 0) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + windowWidthLandscape = metrics.widthPixels; + } + + return windowWidthLandscape; + } + + public static int getNavBarHeight(Context context) { + int result = 0; + boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey(); + boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); + + if(!hasMenuKey && !hasBackKey) { + //The device has a navigation bar + Resources resources = context.getResources(); + + int orientation = context.getResources().getConfiguration().orientation; + int resourceId; + if (isTablet(context)){ + resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android"); + } else { + resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android"); + } + + if (resourceId > 0) { + return context.getResources().getDimensionPixelSize(resourceId); + } + } + return result; + } + + + private static boolean isTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java new file mode 100644 index 000000000..d3bb04f5d --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java @@ -0,0 +1,31 @@ +package com.pchmn.materialchips.views; + + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; + +public class ChipsInputEditText extends android.support.v7.widget.AppCompatEditText { + + private FilterableListView filterableListView; + + public ChipsInputEditText(Context context) { + super(context); + } + + public ChipsInputEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public boolean isFilterableListVisible() { + return filterableListView != null && filterableListView.getVisibility() == VISIBLE; + } + + public FilterableListView getFilterableListView() { + return filterableListView; + } + + public void setFilterableListView(FilterableListView filterableListView) { + this.filterableListView = filterableListView; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java new file mode 100644 index 000000000..b5a7f6eb3 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java @@ -0,0 +1,233 @@ +package com.pchmn.materialchips.views; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ColorUtil; +import com.pchmn.materialchips.util.LetterTileProvider; + + +public class DetailedChipView extends LinearLayout { + + private static final String TAG = DetailedChipView.class.toString(); + // context + private Context mContext; + // xml elements + private LinearLayout mContentLayout; + private TextView mNameTextView; + private TextView mInfoTextView; + private ImageButton mDeleteButton; + // letter tile provider + private static LetterTileProvider mLetterTileProvider; + // attributes + private ColorStateList mBackgroundColor; + + public DetailedChipView(Context context) { + super(context); + mContext = context; + init(null); + } + + public DetailedChipView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(attrs); + } + + /** + * Inflate the view according to attributes + * + * @param attrs the attributes + */ + private void init(AttributeSet attrs) { + // inflate layout + View rootView = inflate(getContext(), R.layout.detailed_chip_view, this); + + mContentLayout = (LinearLayout) rootView.findViewById(R.id.content); + mNameTextView = (TextView) rootView.findViewById(R.id.name); + mInfoTextView = (TextView) rootView.findViewById(R.id.info); + mDeleteButton = (ImageButton) rootView.findViewById(R.id.delete_button); + + // letter tile provider + mLetterTileProvider = new LetterTileProvider(mContext); + + // hide on first + setVisibility(GONE); + // hide on touch outside + hideOnTouchOutside(); + } + + /** + * Hide the view on touch outside of it + */ + private void hideOnTouchOutside() { + // set focusable + setFocusable(true); + setFocusableInTouchMode(true); + setClickable(true); + } + + /** + * Fade in + */ + public void fadeIn() { + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(VISIBLE); + // focus on the view + requestFocus(); + } + + /** + * Fade out + */ + public void fadeOut() { + AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(GONE); + // fix onclick issue + clearFocus(); + setClickable(false); + } + + public void setName(String name) { + mNameTextView.setText(name); + } + + public void setInfo(String info) { + if(info != null) { + mInfoTextView.setVisibility(VISIBLE); + mInfoTextView.setText(info); + } + else { + mInfoTextView.setVisibility(GONE); + } + } + + public void setTextColor(ColorStateList color) { + mNameTextView.setTextColor(color); + mInfoTextView.setTextColor(ColorUtil.alpha(color.getDefaultColor(), 150)); + } + + public void setBackGroundcolor(ColorStateList color) { + mBackgroundColor = color; + mContentLayout.getBackground().setColorFilter(color.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + } + + public int getBackgroundColor() { + return mBackgroundColor == null ? ContextCompat.getColor(mContext, R.color.colorAccent) : mBackgroundColor.getDefaultColor(); + } + + public void setDeleteIconColor(ColorStateList color) { + mDeleteButton.getDrawable().mutate().setColorFilter(color.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + } + + public void setOnDeleteClicked(OnClickListener onClickListener) { + mDeleteButton.setOnClickListener(onClickListener); + } + + public void alignLeft() { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentLayout.getLayoutParams(); + params.leftMargin = 0; + mContentLayout.setLayoutParams(params); + } + + public void alignRight() { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentLayout.getLayoutParams(); + params.rightMargin = 0; + mContentLayout.setLayoutParams(params); + } + + public static class Builder { + private Context context; + private String name; + private String info; + private ColorStateList textColor; + private ColorStateList backgroundColor; + private ColorStateList deleteIconColor; + + public Builder(Context context) { + this.context = context; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder info(String info) { + this.info = info; + return this; + } + + public Builder chip(ChipInterface chip) { + this.name = chip.getLabel(); + this.info = chip.getInfo(); + return this; + } + + public Builder textColor(ColorStateList textColor) { + this.textColor = textColor; + return this; + } + + public Builder backgroundColor(ColorStateList backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public Builder deleteIconColor(ColorStateList deleteIconColor) { + this.deleteIconColor = deleteIconColor; + return this; + } + + public DetailedChipView build() { + return DetailedChipView.newInstance(this); + } + } + + private static DetailedChipView newInstance(Builder builder) { + DetailedChipView detailedChipView = new DetailedChipView(builder.context); + // avatar + // background color + if(builder.backgroundColor != null) + detailedChipView.setBackGroundcolor(builder.backgroundColor); + + // text color + if(builder.textColor != null) + detailedChipView.setTextColor(builder.textColor); + else if(ColorUtil.isColorDark(detailedChipView.getBackgroundColor())) + detailedChipView.setTextColor(ColorStateList.valueOf(Color.WHITE)); + else + detailedChipView.setTextColor(ColorStateList.valueOf(Color.BLACK)); + + // delete icon color + if(builder.deleteIconColor != null) + detailedChipView.setDeleteIconColor(builder.deleteIconColor); + else if(ColorUtil.isColorDark(detailedChipView.getBackgroundColor())) + detailedChipView.setDeleteIconColor(ColorStateList.valueOf(Color.WHITE)); + else + detailedChipView.setDeleteIconColor(ColorStateList.valueOf(Color.BLACK)); + + detailedChipView.setName(builder.name); + detailedChipView.setInfo(builder.info); + return detailedChipView; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java new file mode 100644 index 000000000..ba4034fb7 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java @@ -0,0 +1,156 @@ +package com.pchmn.materialchips.views; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.os.Build; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.AlphaAnimation; +import android.widget.Filter; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.adapter.FilterableAdapter; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ViewUtil; + +import java.util.List; + +public class FilterableListView extends RelativeLayout { + + private static final String TAG = FilterableListView.class.toString(); + private FrameLayout frameLayout; + private Context mContext; + // list + private RecyclerView mRecyclerView; + private FilterableAdapter mAdapter; + private List mFilterableList; + // others + private ChipsInput mChipsInput; + private ViewGroup rootView; + + public FilterableListView(Context context) { + this(context, null); + } + + public FilterableListView(Context context, ViewGroup layout) { + super(context); + this.mContext = context; + this.rootView = layout; + init(); + } + + private void init() { + // inflate layout + View view = inflate(getContext(), R.layout.list_filterable_view, this); + + mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); + + // recycler + mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); + + // hide on first + setVisibility(GONE); + } + + public void build(List filterableList, ChipsInput chipsInput, ColorStateList backgroundColor, ColorStateList textColor) { + mFilterableList = filterableList; + mChipsInput = chipsInput; + + // adapter + mAdapter = new FilterableAdapter(mContext, mRecyclerView, filterableList, chipsInput, backgroundColor, textColor); + mRecyclerView.setAdapter(mAdapter); + if(backgroundColor != null) + mRecyclerView.getBackground().setColorFilter(backgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + + // listen to change in the tree + mChipsInput.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + + @Override + public void onGlobalLayout() { + + // position + if(rootView == null){ + rootView = (ViewGroup) mChipsInput.getRootView(); + } + + // size + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + ViewUtil.getWindowWidth(mContext), + ViewGroup.LayoutParams.WRAP_CONTENT); + + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + layoutParams.bottomMargin = ViewUtil.getNavBarHeight(mContext); + } + + //If this child view is already added to the parent rootView, then remove it first + ViewGroup parent = (ViewGroup) FilterableListView.this.getParent(); + if (parent != null) { + parent.removeView(FilterableListView.this); + } + // add view + rootView.addView(FilterableListView.this, layoutParams); + + + // remove the listener: + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mChipsInput.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mChipsInput.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + } + + }); + } + + public void filterList(CharSequence text) { + mAdapter.getFilter().filter(text, new Filter.FilterListener() { + @Override + public void onFilterComplete(int count) { + // show if there are results + if(mAdapter.getItemCount() > 0) + fadeIn(); + else + fadeOut(); + } + }); + } + + /** + * Fade in + */ + public void fadeIn() { + if(getVisibility() == VISIBLE) + return; + + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(VISIBLE); + } + + /** + * Fade out + */ + public void fadeOut() { + if(getVisibility() == GONE) + return; + + AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(GONE); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java new file mode 100644 index 000000000..db212ff65 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java @@ -0,0 +1,49 @@ +package com.pchmn.materialchips.views; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.widget.NestedScrollView; +import android.util.AttributeSet; + +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.util.ViewUtil; + +public class ScrollViewMaxHeight extends NestedScrollView { + + private int mMaxHeight; + private int mWidthMeasureSpec; + + public ScrollViewMaxHeight(Context context) { + super(context); + } + + public ScrollViewMaxHeight(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.ScrollViewMaxHeight, + 0, 0); + + try { + mMaxHeight = a.getDimensionPixelSize(R.styleable.ScrollViewMaxHeight_maxHeight, ViewUtil.dpToPx(300)); + } + finally { + a.recycle(); + } + } + + public void setMaxHeight(int height) { + mMaxHeight = height; + int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST); + measure(mWidthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mWidthMeasureSpec = widthMeasureSpec; + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml b/extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml new file mode 100644 index 000000000..aea2c3a4f --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml new file mode 100644 index 000000000..58b375ecc --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml new file mode 100644 index 000000000..69dbc5bde --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml new file mode 100644 index 000000000..f9b266324 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml new file mode 100644 index 000000000..e6545bf8a --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml new file mode 100644 index 000000000..69453b4e1 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml new file mode 100644 index 000000000..22ca15668 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml b/extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml new file mode 100644 index 000000000..58b375ecc --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/chip_view.xml b/extern/MaterialChipsInput/src/main/res/layout/chip_view.xml new file mode 100644 index 000000000..34e73036c --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/chip_view.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml b/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml new file mode 100644 index 000000000..f39d6fc70 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml b/extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml new file mode 100644 index 000000000..0533d3e5d --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml b/extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml new file mode 100644 index 000000000..6298b696c --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml b/extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml new file mode 100644 index 000000000..3dc228971 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/attrs.xml b/extern/MaterialChipsInput/src/main/res/values/attrs.xml new file mode 100644 index 000000000..ab9511869 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/values/attrs.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/colors.xml b/extern/MaterialChipsInput/src/main/res/values/colors.xml new file mode 100644 index 000000000..9f540beb8 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + + #E0E0E0 + #009688 + #ababab + #b9ffffff + ?attr/colorAccent + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/strings.xml b/extern/MaterialChipsInput/src/main/res/values/strings.xml new file mode 100644 index 000000000..9b3be0701 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + + #f16364 + #f58559 + #f9a43e + #e4c62e + #67bf74 + #59a2be + #2093cd + #ad62a7 + + + + 17sp + + 32dp + + diff --git a/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java b/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java new file mode 100644 index 000000000..b68d97902 --- /dev/null +++ b/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.pchmn.materialchips; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7c3b44eab..01b3c106c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include ':extern:bouncycastle:core' include ':extern:bouncycastle:pg' include ':extern:bouncycastle:prov' include ':extern:minidns' +include ':extern:MaterialChipsInput:library' // Workaround for Android Gradle Plugin 2.0, as described in http://stackoverflow.com/a/36544850 //include ':libkeychain' From 71c3b71e35b2936557e94d7c13b49be9c49f3c21 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 00:25:02 +0200 Subject: [PATCH 095/124] better MaterialChipsInput, first iteration --- .../ui/EncryptModeAsymmetricFragment.java | 22 +- .../com/pchmn/materialchips/ChipsInput.java | 126 ++++++---- .../RecyclerItemClickListener.java | 51 ++++ .../materialchips/adapter/ChipsAdapter.java | 6 +- .../adapter/FilterableAdapter.java | 236 +++--------------- .../adapter/SimpleChipDropdownAdapter.java | 60 +++++ .../com/pchmn/materialchips/model/Chip.java | 39 --- .../materialchips/model/ChipInterface.java | 5 +- .../pchmn/materialchips/model/SimpleChip.java | 48 ++++ .../views/ChipsInputEditText.java | 10 +- .../materialchips/views/DropdownListView.java | 103 ++++++++ .../views/FilterableListView.java | 156 ------------ .../src/main/res/values/attrs.xml | 2 - 13 files changed, 401 insertions(+), 463 deletions(-) create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java delete mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java delete mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java 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 d8d9e7629..681ad4a03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -36,15 +36,16 @@ import android.widget.ViewAnimator; import com.pchmn.materialchips.ChipsInput; import com.pchmn.materialchips.ChipsInput.ChipsListener; -import com.pchmn.materialchips.model.Chip; +import com.pchmn.materialchips.adapter.SimpleChipDropdownAdapter; import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.model.SimpleChip; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.daos.KeyRepository; -import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; @@ -153,13 +154,14 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { } } - private void onLoadEncryptRecipients(List keyInfoChips) { - mEncryptKeyView.setFilterableList(keyInfoChips); + private void onLoadEncryptRecipients(List keyInfoChips) { + SimpleChipDropdownAdapter chipDropdownAdapter = new SimpleChipDropdownAdapter(requireContext(), keyInfoChips); + mEncryptKeyView.setChipDropdownAdapter(chipDropdownAdapter); } public static class EncryptModeViewModel extends ViewModel { private LiveData> signKeyLiveData; - private LiveData> encryptRecipientLiveData; + private LiveData> encryptRecipientLiveData; LiveData> getSignKeyLiveData(Context context) { if (signKeyLiveData == null) { @@ -171,14 +173,14 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { return signKeyLiveData; } - LiveData> getEncryptRecipientLiveData(Context context) { + LiveData> getEncryptRecipientLiveData(Context context) { if (encryptRecipientLiveData == null) { encryptRecipientLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); List keyInfos = keyRepository.getAllUnifiedKeyInfo(); - ArrayList result = new ArrayList<>(); + ArrayList result = new ArrayList<>(); for (UnifiedKeyInfo keyInfo : keyInfos) { - result.add(new Chip(keyInfo.master_key_id(), keyInfo.name(), keyInfo.email())); + result.add(new SimpleChip(keyInfo.master_key_id(), keyInfo.name(), keyInfo.email(), keyInfo.user_id_list())); } return result; }); @@ -206,7 +208,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { try { CanonicalizedPublicKeyRing ring = keyRepository.getCanonicalizedPublicKeyRing(preselectedId); - Chip infooo = new Chip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo"); + SimpleChip infooo = new SimpleChip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo", null); mEncryptKeyView.addChip(infooo); } catch (NotFoundException e) { Timber.e(e, "key not found for encryption!"); diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java index d1e7ad18d..8147b1194 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -1,14 +1,17 @@ package com.pchmn.materialchips; +import java.util.ArrayList; +import java.util.List; + import android.app.Activity; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.net.Uri; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; @@ -17,27 +20,25 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; +import android.widget.Filter.FilterListener; import android.widget.RelativeLayout; import android.widget.TextView; import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager; +import com.pchmn.materialchips.RecyclerItemClickListener.OnItemClickListener; import com.pchmn.materialchips.adapter.ChipsAdapter; -import com.pchmn.materialchips.model.Chip; +import com.pchmn.materialchips.adapter.FilterableAdapter; import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.model.SimpleChip; import com.pchmn.materialchips.util.ActivityUtil; import com.pchmn.materialchips.util.MyWindowCallback; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DetailedChipView; -import com.pchmn.materialchips.views.FilterableListView; +import com.pchmn.materialchips.views.DropdownListView; import com.pchmn.materialchips.views.ScrollViewMaxHeight; -import java.util.ArrayList; -import java.util.List; - public class ChipsInput extends ScrollViewMaxHeight { - - private static final String TAG = ChipsInput.class.toString(); // context private Context mContext; // xml element @@ -59,18 +60,15 @@ public class ChipsInput extends ScrollViewMaxHeight { private ColorStateList mChipDetailedTextColor; private ColorStateList mChipDetailedDeleteIconColor; private ColorStateList mChipDetailedBackgroundColor; - private ColorStateList mFilterableListBackgroundColor; - private ColorStateList mFilterableListTextColor; // chips listener private List mChipsListenerList = new ArrayList<>(); - private ChipsListener mChipsListener; // chip list - private List mFilterableChipList; - private FilterableListView mFilterableListView; + private DropdownListView mDropdownListView; // chip validator private ChipValidator mChipValidator; private ViewGroup filterableListLayout; private ChipsInputEditText mEditText; + private ChipDropdownAdapter filterableAdapter; public ChipsInput(Context context) { super(context); @@ -93,7 +91,7 @@ public class ChipsInput extends ScrollViewMaxHeight { // inflate filterableListLayout View rootView = inflate(getContext(), R.layout.chips_input, this); - mRecyclerView = (RecyclerView) rootView.findViewById(R.id.chips_recycler); + mRecyclerView = rootView.findViewById(R.id.chips_recycler); initEditText(); @@ -128,9 +126,6 @@ public class ChipsInput extends ScrollViewMaxHeight { mChipDetailedTextColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_textColor); mChipDetailedBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_backgroundColor); mChipDetailedDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_deleteIconColor); - // filterable list - mFilterableListBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_backgroundColor); - mFilterableListTextColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_textColor); } finally { a.recycle(); } @@ -224,13 +219,13 @@ public class ChipsInput extends ScrollViewMaxHeight { mChipsAdapter.addChip(chip); } - public void addChip(Object id, String label, String info) { - Chip chip = new Chip(id, label, info); + public void addChip(Object id, String label, String info, String filterString) { + SimpleChip chip = new SimpleChip(id, label, info, filterString); mChipsAdapter.addChip(chip); } public void addChip(String label, String info) { - Chip chip = new Chip(label, info); + SimpleChip chip = new SimpleChip(label, info); mChipsAdapter.addChip(chip); } @@ -280,7 +275,6 @@ public class ChipsInput extends ScrollViewMaxHeight { public void addChipsListener(ChipsListener chipsListener) { mChipsListenerList.add(chipsListener); - mChipsListener = chipsListener; } public void onChipAdded(ChipInterface chip, int size) { @@ -296,25 +290,35 @@ public class ChipsInput extends ScrollViewMaxHeight { } public void onTextChanged(CharSequence text) { - if (mChipsListener != null) { - for (ChipsListener chipsListener : mChipsListenerList) { - chipsListener.onTextChanged(text); - } - // show filterable list - if (mFilterableListView != null) { - if (text.length() > 0) - mFilterableListView.filterList(text); - else - mFilterableListView.fadeOut(); + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onTextChanged(text); + } + // show filterable list + if (mDropdownListView != null) { + if (text.length() > 0) { + filterDropdownList(text); + } else { + mDropdownListView.fadeOut(); } } } - public void onActionDone(CharSequence text) { - if (mChipsListener != null) { - for (ChipsListener chipsListener : mChipsListenerList) { - chipsListener.onActionDone(text); + public void filterDropdownList(CharSequence text) { + filterableAdapter.getFilter().filter(text, new FilterListener() { + @Override + public void onFilterComplete(int count) { + // show if there are results + if (filterableAdapter.getItemCount() > 0) + mDropdownListView.fadeIn(); + else + mDropdownListView.fadeOut(); } + }); + } + + public void onActionDone(CharSequence text) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onActionDone(text); } } @@ -388,19 +392,51 @@ public class ChipsInput extends ScrollViewMaxHeight { this.filterableListLayout = layout; } - public void setFilterableList(List list) { - mFilterableChipList = list; - if (filterableListLayout != null) { - mFilterableListView = new FilterableListView(mContext, filterableListLayout); - } else { - mFilterableListView = new FilterableListView(mContext); + public abstract static class ChipDropdownAdapter + extends FilterableAdapter { + public ChipDropdownAdapter(List itemList) { + super(itemList); } - mFilterableListView.build(mFilterableChipList, this, mFilterableListBackgroundColor, mFilterableListTextColor); - mChipsAdapter.setFilterableListView(mFilterableListView); } - public List getFilterableList() { - return mFilterableChipList; + public void setChipDropdownAdapter(final ChipDropdownAdapter filterableAdapter) { + this.filterableAdapter = filterableAdapter; + if (filterableListLayout != null) { + mDropdownListView = new DropdownListView(mContext, filterableListLayout); + } else { + mDropdownListView = new DropdownListView(mContext, this); + } + mDropdownListView.build(filterableAdapter); + mChipsAdapter.setFilterableListView(mDropdownListView); + mDropdownListView.getRecyclerView().addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + ChipInterface item = filterableAdapter.getItem(position); + addChip(item); + } + })); + + addChipsListener(new ChipsInput.ChipsListener() { + @Override + public void onChipAdded(ChipInterface chip, int newSize) { + filterableAdapter.hideItem((T) chip); + } + + @Override + public void onChipRemoved(ChipInterface chip, int newSize) { + filterableAdapter.unhideItem((T) chip); + } + + @Override + public void onTextChanged(CharSequence text) { + mDropdownListView.getRecyclerView().scrollToPosition(0); + } + + @Override + public void onActionDone(CharSequence text) { + mDropdownListView.getRecyclerView().scrollToPosition(0); + } + }); } public ChipValidator getChipValidator() { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java new file mode 100644 index 000000000..2854f9ebf --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java @@ -0,0 +1,51 @@ +package com.pchmn.materialchips; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener { + private OnItemClickListener mListener; + private boolean mIgnoreTouch = false; + + public interface OnItemClickListener { + void onItemClick(View view, int position); + } + + private GestureDetector mGestureDetector; + + public RecyclerItemClickListener(Context context, OnItemClickListener listener) { + mListener = listener; + mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + }); + } + + @Override + public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { + if (mIgnoreTouch) { + return false; + } + View childView = view.findChildViewUnder(e.getX(), e.getY()); + if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { + mListener.onItemClick(childView, view.getChildAdapterPosition(childView)); + return true; + } + return false; + } + + @Override + public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { + // TODO: should we move mListener.onItemClick here + } + + @Override + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + mIgnoreTouch = disallowIntercept; + } +} \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java index 64d8216a1..f20462257 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -15,7 +15,7 @@ import com.pchmn.materialchips.model.ChipInterface; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DetailedChipView; -import com.pchmn.materialchips.views.FilterableListView; +import com.pchmn.materialchips.views.DropdownListView; import java.util.ArrayList; import java.util.Iterator; @@ -225,9 +225,9 @@ public class ChipsAdapter extends RecyclerView.Adapter detailedChipView.fadeIn(); } - public void setFilterableListView(FilterableListView filterableListView) { + public void setFilterableListView(DropdownListView dropdownListView) { if (mEditText != null) { - mEditText.setFilterableListView(filterableListView); + mEditText.setFilterableListView(dropdownListView); } } diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java index 2895a4252..b7816050b 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java @@ -1,186 +1,47 @@ package com.pchmn.materialchips.adapter; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff; +import java.util.ArrayList; +import java.util.List; + import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import android.widget.Filter; import android.widget.Filterable; -import android.widget.TextView; -import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.R; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.util.ColorUtil; -import com.pchmn.materialchips.util.LetterTileProvider; +import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; +public abstract class FilterableAdapter + extends RecyclerView.Adapter implements Filterable { + private List displayedList = new ArrayList<>(); + private List hiddenItemsList = new ArrayList<>(); + private ItemFilter itemFilter; -import static android.view.View.GONE; - -public class FilterableAdapter extends RecyclerView.Adapter implements Filterable { - - private static final String TAG = FilterableAdapter.class.toString(); - // context - private Context mContext; - // list - private List mOriginalList = new ArrayList<>(); - private List mChipList = new ArrayList<>(); - private List mFilteredList = new ArrayList<>(); - private ChipFilter mFilter; - private ChipsInput mChipsInput; - private LetterTileProvider mLetterTileProvider; - private ColorStateList mBackgroundColor; - private ColorStateList mTextColor; - // recycler - private RecyclerView mRecyclerView; - // sort - private Comparator mComparator; - private Collator mCollator; - - - public FilterableAdapter(Context context, - RecyclerView recyclerView, - List chipList, - ChipsInput chipsInput, - ColorStateList backgroundColor, - ColorStateList textColor) { - mContext = context; - mRecyclerView = recyclerView; - mCollator = Collator.getInstance(Locale.getDefault()); - mCollator.setStrength(Collator.PRIMARY); - mComparator = new Comparator() { - @Override - public int compare(ChipInterface o1, ChipInterface o2) { - return mCollator.compare(o1.getLabel(), o2.getLabel()); - } - }; - // remove chips that do not have label - Iterator iterator = chipList.iterator(); - while (iterator.hasNext()) { - if (iterator.next().getLabel() == null) - iterator.remove(); - } - sortList(chipList); - mOriginalList.addAll(chipList); - mChipList.addAll(chipList); - mFilteredList.addAll(chipList); - mLetterTileProvider = new LetterTileProvider(mContext); - mBackgroundColor = backgroundColor; - mTextColor = textColor; - mChipsInput = chipsInput; - - mChipsInput.addChipsListener(new ChipsInput.ChipsListener() { - @Override - public void onChipAdded(ChipInterface chip, int newSize) { - removeChip(chip); - } - - @Override - public void onChipRemoved(ChipInterface chip, int newSize) { - addChip(chip); - } - - @Override - public void onTextChanged(CharSequence text) { - mRecyclerView.scrollToPosition(0); - } - - @Override - public void onActionDone(CharSequence text) { - mRecyclerView.scrollToPosition(0); - } - }); - } - - private class ItemViewHolder extends RecyclerView.ViewHolder { - - private TextView mLabel; - private TextView mInfo; - - ItemViewHolder(View view) { - super(view); - mLabel = (TextView) view.findViewById(R.id.label); - mInfo = (TextView) view.findViewById(R.id.info); - } - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(mContext).inflate(R.layout.item_list_filterable, parent, false); - return new ItemViewHolder(view); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - ItemViewHolder itemViewHolder = (ItemViewHolder) holder; - final ChipInterface chip = getItem(position); - - // label - itemViewHolder.mLabel.setText(chip.getLabel()); - - // info - if (chip.getInfo() != null) { - itemViewHolder.mInfo.setVisibility(View.VISIBLE); - itemViewHolder.mInfo.setText(chip.getInfo()); - } else { - itemViewHolder.mInfo.setVisibility(GONE); - } - - // colors - if (mBackgroundColor != null) - itemViewHolder.itemView.getBackground().setColorFilter(mBackgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); - if (mTextColor != null) { - itemViewHolder.mLabel.setTextColor(mTextColor); - itemViewHolder.mInfo.setTextColor(ColorUtil.alpha(mTextColor.getDefaultColor(), 150)); - } - - // onclick - itemViewHolder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mChipsInput != null) - mChipsInput.addChip(chip); - } - }); + public FilterableAdapter(List itemList) { + itemFilter = new ItemFilter(itemList); + displayedList.addAll(itemList); } @Override public int getItemCount() { - return mFilteredList.size(); + return displayedList.size(); } - private ChipInterface getItem(int position) { - return mFilteredList.get(position); + public T getItem(int position) { + return displayedList.get(position); } @Override public Filter getFilter() { - if (mFilter == null) - mFilter = new ChipFilter(this, mChipList); - return mFilter; + return itemFilter; } - private class ChipFilter extends Filter { + private class ItemFilter extends Filter { + private List originalList; + private List filteredList; - private FilterableAdapter adapter; - private List originalList; - private List filteredList; - - public ChipFilter(FilterableAdapter adapter, List originalList) { + ItemFilter(List chipList) { super(); - this.adapter = adapter; - this.originalList = originalList; + this.originalList = new ArrayList<>(chipList); this.filteredList = new ArrayList<>(); } @@ -188,15 +49,13 @@ public class FilterableAdapter extends RecyclerView.Adapter) results.values); + FilterableAdapter.this.displayedList.clear(); + FilterableAdapter.this.displayedList.addAll((ArrayList) results.values); notifyDataSetChanged(); } } - private void removeChip(ChipInterface chip) { - int position = mFilteredList.indexOf(chip); - if (position >= 0) - mFilteredList.remove(position); - - position = mChipList.indexOf(chip); - if (position >= 0) - mChipList.remove(position); - + public void hideItem(T item) { + if (!hiddenItemsList.contains(item)) { + hiddenItemsList.add(item); + } notifyDataSetChanged(); } - private void addChip(ChipInterface chip) { - if (contains(chip)) { - mChipList.add(chip); - mFilteredList.add(chip); - // sort original list - sortList(mChipList); - // sort filtered list - sortList(mFilteredList); - - notifyDataSetChanged(); - } + public void unhideItem(T item) { + hiddenItemsList.remove(item); + notifyDataSetChanged(); } - private boolean contains(ChipInterface chip) { - for (ChipInterface item : mOriginalList) { - if (item.equals(chip)) - return true; - } - return false; - } - - private void sortList(List list) { - Collections.sort(list, mComparator); + public interface FilterableItem { + boolean isKeptForConstraint(CharSequence constraint); } } diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java new file mode 100644 index 000000000..cdf278088 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java @@ -0,0 +1,60 @@ +package com.pchmn.materialchips.adapter; + + +import java.util.List; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.pchmn.materialchips.ChipsInput.ChipDropdownAdapter; +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.adapter.SimpleChipDropdownAdapter.ItemViewHolder; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.model.SimpleChip; + + +public class SimpleChipDropdownAdapter extends ChipDropdownAdapter { + private final LayoutInflater layoutInflater; + + public SimpleChipDropdownAdapter(Context context, List keyInfoChips) { + super(keyInfoChips); + + layoutInflater = LayoutInflater.from(context); + } + + class ItemViewHolder extends RecyclerView.ViewHolder { + private TextView mLabel; + private TextView mInfo; + + ItemViewHolder(View view) { + super(view); + mLabel = view.findViewById(com.pchmn.materialchips.R.id.label); + mInfo = view.findViewById(com.pchmn.materialchips.R.id.info); + } + } + + @NonNull + @Override + public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = layoutInflater.inflate(R.layout.item_list_filterable, parent, false); + return new ItemViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { + ChipInterface chip = getItem(position); + + holder.mLabel.setText(chip.getLabel()); + if (chip.getInfo() != null) { + holder.mInfo.setVisibility(View.VISIBLE); + holder.mInfo.setText(chip.getInfo()); + } else { + holder.mInfo.setVisibility(View.GONE); + } + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java deleted file mode 100644 index e79c79cd9..000000000 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.pchmn.materialchips.model; - - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -public class Chip implements ChipInterface { - - private Object id; - private String label; - private String info; - - public Chip(@NonNull Object id, @NonNull String label, @Nullable String info) { - this.id = id; - this.label = label; - this.info = info; - } - - - public Chip(@NonNull String label, @Nullable String info) { - this.label = label; - this.info = info; - } - - @Override - public Object getId() { - return id; - } - - @Override - public String getLabel() { - return label; - } - - @Override - public String getInfo() { - return info; - } -} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java index 804ef47f1..edb12b590 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java @@ -1,11 +1,10 @@ package com.pchmn.materialchips.model; -import android.graphics.drawable.Drawable; -import android.net.Uri; +import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; -public interface ChipInterface { +public interface ChipInterface extends FilterableItem { Object getId(); String getLabel(); String getInfo(); diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java new file mode 100644 index 000000000..acd740b17 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java @@ -0,0 +1,48 @@ +package com.pchmn.materialchips.model; + + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; + + +public class SimpleChip implements ChipInterface { + + private Object id; + private String label; + private String info; + private String filterString; + + public SimpleChip(@NonNull Object id, @NonNull String label, @Nullable String info, @Nullable String filterString) { + this.id = id; + this.label = label; + this.info = info; + this.filterString = filterString != null ? filterString.toLowerCase() : label.toLowerCase(); + } + + public SimpleChip(@NonNull String label, @Nullable String info) { + this.label = label; + this.info = info; + } + + @Override + public Object getId() { + return id; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public String getInfo() { + return info; + } + + @Override + public boolean isKeptForConstraint(CharSequence constraint) { + return filterString.contains(constraint); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java index d3bb04f5d..9c2b108c5 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java @@ -4,10 +4,12 @@ package com.pchmn.materialchips.views; import android.content.Context; import android.util.AttributeSet; import android.util.Log; +import android.view.View; + public class ChipsInputEditText extends android.support.v7.widget.AppCompatEditText { - private FilterableListView filterableListView; + private View filterableListView; public ChipsInputEditText(Context context) { super(context); @@ -21,11 +23,7 @@ public class ChipsInputEditText extends android.support.v7.widget.AppCompatEditT return filterableListView != null && filterableListView.getVisibility() == VISIBLE; } - public FilterableListView getFilterableListView() { - return filterableListView; - } - - public void setFilterableListView(FilterableListView filterableListView) { + public void setFilterableListView(View filterableListView) { this.filterableListView = filterableListView; } } diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java new file mode 100644 index 000000000..d44019dac --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java @@ -0,0 +1,103 @@ +package com.pchmn.materialchips.views; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.AlphaAnimation; +import android.widget.RelativeLayout; + +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.adapter.FilterableAdapter; +import com.pchmn.materialchips.util.ViewUtil; + + +@SuppressLint("ViewConstructor") // this is a dropdown view, it doesn't come up in preview +public class DropdownListView extends RelativeLayout { + private RecyclerView recyclerView; + private ViewGroup rootView; + + public DropdownListView(Context context, ViewGroup layout) { + super(context); + this.rootView = layout; + init(); + } + + private void init() { + View view = inflate(getContext(), R.layout.list_filterable_view, this); + + recyclerView = view.findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + + setVisibility(GONE); + } + + public void build(FilterableAdapter filterableAdapter) { + recyclerView.setAdapter(filterableAdapter); + + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // size + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + ViewUtil.getWindowWidth(getContext()), + ViewGroup.LayoutParams.WRAP_CONTENT); + + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + layoutParams.bottomMargin = ViewUtil.getNavBarHeight(getContext()); + } + + // If this child view is already added to the parent rootView, then remove it first + ViewGroup parent = (ViewGroup) DropdownListView.this.getParent(); + if (parent != null) { + parent.removeView(DropdownListView.this); + } + // add view + rootView.addView(DropdownListView.this, layoutParams); + + // remove the listener: + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + } + + }); + } + + public RecyclerView getRecyclerView() { + return recyclerView; + } + + public void fadeIn() { + if (getVisibility() == VISIBLE) { + return; + } + + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(VISIBLE); + } + + public void fadeOut() { + if (getVisibility() == GONE) { + return; + } + + AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(GONE); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java deleted file mode 100644 index ba4034fb7..000000000 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.pchmn.materialchips.views; - - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.os.Build; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.animation.AlphaAnimation; -import android.widget.Filter; -import android.widget.FrameLayout; -import android.widget.RelativeLayout; - -import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.R; -import com.pchmn.materialchips.adapter.FilterableAdapter; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.util.ViewUtil; - -import java.util.List; - -public class FilterableListView extends RelativeLayout { - - private static final String TAG = FilterableListView.class.toString(); - private FrameLayout frameLayout; - private Context mContext; - // list - private RecyclerView mRecyclerView; - private FilterableAdapter mAdapter; - private List mFilterableList; - // others - private ChipsInput mChipsInput; - private ViewGroup rootView; - - public FilterableListView(Context context) { - this(context, null); - } - - public FilterableListView(Context context, ViewGroup layout) { - super(context); - this.mContext = context; - this.rootView = layout; - init(); - } - - private void init() { - // inflate layout - View view = inflate(getContext(), R.layout.list_filterable_view, this); - - mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); - - // recycler - mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); - - // hide on first - setVisibility(GONE); - } - - public void build(List filterableList, ChipsInput chipsInput, ColorStateList backgroundColor, ColorStateList textColor) { - mFilterableList = filterableList; - mChipsInput = chipsInput; - - // adapter - mAdapter = new FilterableAdapter(mContext, mRecyclerView, filterableList, chipsInput, backgroundColor, textColor); - mRecyclerView.setAdapter(mAdapter); - if(backgroundColor != null) - mRecyclerView.getBackground().setColorFilter(backgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); - - // listen to change in the tree - mChipsInput.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - - @Override - public void onGlobalLayout() { - - // position - if(rootView == null){ - rootView = (ViewGroup) mChipsInput.getRootView(); - } - - // size - RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( - ViewUtil.getWindowWidth(mContext), - ViewGroup.LayoutParams.WRAP_CONTENT); - - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); - - if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - layoutParams.bottomMargin = ViewUtil.getNavBarHeight(mContext); - } - - //If this child view is already added to the parent rootView, then remove it first - ViewGroup parent = (ViewGroup) FilterableListView.this.getParent(); - if (parent != null) { - parent.removeView(FilterableListView.this); - } - // add view - rootView.addView(FilterableListView.this, layoutParams); - - - // remove the listener: - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - mChipsInput.getViewTreeObserver().removeGlobalOnLayoutListener(this); - } else { - mChipsInput.getViewTreeObserver().removeOnGlobalLayoutListener(this); - } - } - - }); - } - - public void filterList(CharSequence text) { - mAdapter.getFilter().filter(text, new Filter.FilterListener() { - @Override - public void onFilterComplete(int count) { - // show if there are results - if(mAdapter.getItemCount() > 0) - fadeIn(); - else - fadeOut(); - } - }); - } - - /** - * Fade in - */ - public void fadeIn() { - if(getVisibility() == VISIBLE) - return; - - AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); - anim.setDuration(200); - startAnimation(anim); - setVisibility(VISIBLE); - } - - /** - * Fade out - */ - public void fadeOut() { - if(getVisibility() == GONE) - return; - - AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); - anim.setDuration(200); - startAnimation(anim); - setVisibility(GONE); - } -} diff --git a/extern/MaterialChipsInput/src/main/res/values/attrs.xml b/extern/MaterialChipsInput/src/main/res/values/attrs.xml index ab9511869..7c49f83a0 100644 --- a/extern/MaterialChipsInput/src/main/res/values/attrs.xml +++ b/extern/MaterialChipsInput/src/main/res/values/attrs.xml @@ -24,8 +24,6 @@ - - From 355a4eaa0fb37c691497c64bc8e233bdf6d4d9eb Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 10:47:42 +0200 Subject: [PATCH 096/124] extract DelegateWindowCallback superclass for MyWindowCallback --- .../com/pchmn/materialchips/ChipsInput.java | 13 +- .../util/DelegateWindowCallback.java | 147 +++++++++++++++++ .../materialchips/util/MyWindowCallback.java | 148 ++---------------- 3 files changed, 165 insertions(+), 143 deletions(-) create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java index 8147b1194..cfa962a44 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -41,9 +41,6 @@ import com.pchmn.materialchips.views.ScrollViewMaxHeight; public class ChipsInput extends ScrollViewMaxHeight { // context private Context mContext; - // xml element - private RecyclerView mRecyclerView; - // adapter private ChipsAdapter mChipsAdapter; // attributes private static final int NONE = -1; @@ -91,7 +88,7 @@ public class ChipsInput extends ScrollViewMaxHeight { // inflate filterableListLayout View rootView = inflate(getContext(), R.layout.chips_input, this); - mRecyclerView = rootView.findViewById(R.id.chips_recycler); + RecyclerView recyclerView = rootView.findViewById(R.id.chips_recycler); initEditText(); @@ -132,13 +129,13 @@ public class ChipsInput extends ScrollViewMaxHeight { } // adapter - mChipsAdapter = new ChipsAdapter(mContext, this, mEditText, mRecyclerView); + mChipsAdapter = new ChipsAdapter(mContext, this, mEditText, recyclerView); ChipsLayoutManager chipsLayoutManager = ChipsLayoutManager.newBuilder(mContext) .setOrientation(ChipsLayoutManager.HORIZONTAL) .build(); - mRecyclerView.setLayoutManager(chipsLayoutManager); - mRecyclerView.setNestedScrollingEnabled(false); - mRecyclerView.setAdapter(mChipsAdapter); + recyclerView.setLayoutManager(chipsLayoutManager); + recyclerView.setNestedScrollingEnabled(false); + recyclerView.setAdapter(mChipsAdapter); // set window callback // will hide DetailedOpenView and hide keyboard on touch outside diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java new file mode 100644 index 000000000..3dad594c3 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java @@ -0,0 +1,147 @@ +package com.pchmn.materialchips.util; + + +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.view.ActionMode; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.View; +import android.view.Window; +import android.view.Window.Callback; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; + + +public abstract class DelegateWindowCallback implements Window.Callback { + private Window.Callback delegateCallback; + + public DelegateWindowCallback(Callback delegateCallback) { + this.delegateCallback = delegateCallback; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent keyEvent) { + return delegateCallback.dispatchKeyEvent(keyEvent); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) { + return delegateCallback.dispatchKeyShortcutEvent(keyEvent); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent motionEvent) { + return delegateCallback.dispatchTouchEvent(motionEvent); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent motionEvent) { + return delegateCallback.dispatchTrackballEvent(motionEvent); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) { + return delegateCallback.dispatchGenericMotionEvent(motionEvent); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + return delegateCallback.dispatchPopulateAccessibilityEvent(accessibilityEvent); + } + + @Nullable + @Override + public View onCreatePanelView(int i) { + return delegateCallback.onCreatePanelView(i); + } + + @Override + public boolean onCreatePanelMenu(int i, Menu menu) { + return delegateCallback.onCreatePanelMenu(i, menu); + } + + @Override + public boolean onPreparePanel(int i, View view, Menu menu) { + return delegateCallback.onPreparePanel(i, view, menu); + } + + @Override + public boolean onMenuOpened(int i, Menu menu) { + return delegateCallback.onMenuOpened(i, menu); + } + + @Override + public boolean onMenuItemSelected(int i, MenuItem menuItem) { + return delegateCallback.onMenuItemSelected(i, menuItem); + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) { + delegateCallback.onWindowAttributesChanged(layoutParams); + } + + @Override + public void onContentChanged() { + delegateCallback.onContentChanged(); + } + + @Override + public void onWindowFocusChanged(boolean b) { + delegateCallback.onWindowFocusChanged(b); + } + + @Override + public void onAttachedToWindow() { + delegateCallback.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + delegateCallback.onDetachedFromWindow(); + } + + @Override + public void onPanelClosed(int i, Menu menu) { + delegateCallback.onPanelClosed(i, menu); + } + + @Override + public boolean onSearchRequested() { + return delegateCallback.onSearchRequested(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return delegateCallback.onSearchRequested(searchEvent); + } + + @Nullable + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return delegateCallback.onWindowStartingActionMode(callback); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Nullable + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) { + return delegateCallback.onWindowStartingActionMode(callback, i); + } + + @Override + public void onActionModeStarted(ActionMode actionMode) { + delegateCallback.onActionModeStarted(actionMode); + } + + @Override + public void onActionModeFinished(ActionMode actionMode) { + delegateCallback.onActionModeFinished(actionMode); + } + +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java index 924350801..b6e819c6e 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java @@ -3,49 +3,26 @@ package com.pchmn.materialchips.util; import android.app.Activity; import android.content.Context; import android.graphics.Rect; -import android.os.Build; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.view.ActionMode; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; import android.view.MotionEvent; -import android.view.SearchEvent; import android.view.View; import android.view.Window; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DetailedChipView; -public class MyWindowCallback implements Window.Callback { +public class MyWindowCallback extends DelegateWindowCallback { + private Activity activity; - private Window.Callback mLocalCallback; - private Activity mActivity; - - public MyWindowCallback(Window.Callback localCallback, Activity activity) { - mLocalCallback = localCallback; - mActivity = activity; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent keyEvent) { - return mLocalCallback.dispatchKeyEvent(keyEvent); - } - - @Override - public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) { - return mLocalCallback.dispatchKeyShortcutEvent(keyEvent); + public MyWindowCallback(Window.Callback delegateCallback, Activity activity) { + super(delegateCallback); + this.activity = activity; } @Override public boolean dispatchTouchEvent(MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - View v = mActivity.getCurrentFocus(); + View v = activity.getCurrentFocus(); if(v instanceof DetailedChipView) { Rect outRect = new Rect(); v.getGlobalVisibleRect(outRect); @@ -58,116 +35,17 @@ public class MyWindowCallback implements Window.Callback { v.getGlobalVisibleRect(outRect); if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY()) && !((ChipsInputEditText) v).isFilterableListVisible()) { - InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + hideKeyboard(v); } } } - return mLocalCallback.dispatchTouchEvent(motionEvent); + return super.dispatchTouchEvent(motionEvent); } - @Override - public boolean dispatchTrackballEvent(MotionEvent motionEvent) { - return mLocalCallback.dispatchTrackballEvent(motionEvent); - } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) { - return mLocalCallback.dispatchGenericMotionEvent(motionEvent); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) { - return mLocalCallback.dispatchPopulateAccessibilityEvent(accessibilityEvent); - } - - @Nullable - @Override - public View onCreatePanelView(int i) { - return mLocalCallback.onCreatePanelView(i); - } - - @Override - public boolean onCreatePanelMenu(int i, Menu menu) { - return mLocalCallback.onCreatePanelMenu(i, menu); - } - - @Override - public boolean onPreparePanel(int i, View view, Menu menu) { - return mLocalCallback.onPreparePanel(i, view, menu); - } - - @Override - public boolean onMenuOpened(int i, Menu menu) { - return mLocalCallback.onMenuOpened(i, menu); - } - - @Override - public boolean onMenuItemSelected(int i, MenuItem menuItem) { - return mLocalCallback.onMenuItemSelected(i, menuItem); - } - - @Override - public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) { - mLocalCallback.onWindowAttributesChanged(layoutParams); - } - - @Override - public void onContentChanged() { - mLocalCallback.onContentChanged(); - } - - @Override - public void onWindowFocusChanged(boolean b) { - mLocalCallback.onWindowFocusChanged(b); - } - - @Override - public void onAttachedToWindow() { - mLocalCallback.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow() { - mLocalCallback.onDetachedFromWindow(); - } - - @Override - public void onPanelClosed(int i, Menu menu) { - mLocalCallback.onPanelClosed(i, menu); - } - - @Override - public boolean onSearchRequested() { - return mLocalCallback.onSearchRequested(); - } - - @RequiresApi(api = Build.VERSION_CODES.M) - @Override - public boolean onSearchRequested(SearchEvent searchEvent) { - return mLocalCallback.onSearchRequested(searchEvent); - } - - @Nullable - @Override - public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { - return mLocalCallback.onWindowStartingActionMode(callback); - } - - @RequiresApi(api = Build.VERSION_CODES.M) - @Nullable - @Override - public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) { - return mLocalCallback.onWindowStartingActionMode(callback, i); - } - - @Override - public void onActionModeStarted(ActionMode actionMode) { - mLocalCallback.onActionModeStarted(actionMode); - } - - @Override - public void onActionModeFinished(ActionMode actionMode) { - mLocalCallback.onActionModeFinished(actionMode); + private void hideKeyboard(View v) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } } } From 93e8c74ec6f1ac4288624bdbade31aa41c15eb41 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 20:12:09 +0200 Subject: [PATCH 097/124] generify ChipsInput, extract SimpleChipsInput --- .../ui/EncryptModeAsymmetricFragment.java | 70 ++-- .../ui/chips/EncryptRecipientChipsInput.java | 53 +++ .../layout/encrypt_asymmetric_fragment.xml | 3 +- .../com/pchmn/materialchips/ChipsInput.java | 283 +++++----------- .../materialchips/adapter/ChipsAdapter.java | 303 +++++++----------- .../{model => simple}/SimpleChip.java | 4 +- .../SimpleChipDropdownAdapter.java | 11 +- .../simple/SimpleChipsAdapter.java | 58 ++++ .../simple/SimpleChipsInput.java | 32 ++ ...allback.java => ClickOutsideCallback.java} | 4 +- .../src/main/res/values/attrs.xml | 6 - 11 files changed, 376 insertions(+), 451 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java rename extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/{model => simple}/SimpleChip.java (92%) rename extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/{adapter => simple}/SimpleChipDropdownAdapter.java (83%) create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsAdapter.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java rename extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/{MyWindowCallback.java => ClickOutsideCallback.java} (91%) 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 681ad4a03..d1523e9d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -34,31 +34,29 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ViewAnimator; -import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.ChipsInput.ChipsListener; -import com.pchmn.materialchips.adapter.SimpleChipDropdownAdapter; +import com.pchmn.materialchips.ChipsInput.SimpleChipsListener; import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.model.SimpleChip; +import com.pchmn.materialchips.simple.SimpleChip; +import com.pchmn.materialchips.simple.SimpleChipsInput; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.daos.KeyRepository; -import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Passphrase; -import timber.log.Timber; public class EncryptModeAsymmetricFragment extends EncryptModeFragment { KeyRepository keyRepository; private KeySpinner mSignKeySpinner; - private ChipsInput mEncryptKeyView; + private SimpleChipsInput mEncryptKeyView; public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; @@ -105,31 +103,21 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner.setShowNone(R.string.cert_none); final ViewAnimator vEncryptionIcon = view.findViewById(R.id.result_encryption_icon); - mEncryptKeyView.addChipsListener(new ChipsListener() { + mEncryptKeyView.addChipsListener(new SimpleChipsListener() { @Override - public void onChipAdded(ChipInterface chipInterface, int newSize) { + public void onChipAdded(SimpleChip chipInterface, int newSize) { if (vEncryptionIcon.getDisplayedChild() != 1) { vEncryptionIcon.setDisplayedChild(1); } } @Override - public void onChipRemoved(ChipInterface chipInterface, int newSize) { + public void onChipRemoved(SimpleChip chipInterface, int newSize) { int child = newSize == 0 ? 0 : 1; if (vEncryptionIcon.getDisplayedChild() != child) { vEncryptionIcon.setDisplayedChild(child); } } - - @Override - public void onTextChanged(CharSequence charSequence) { - - } - - @Override - public void onActionDone(CharSequence charSequence) { - - } }); return view; @@ -141,7 +129,13 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { EncryptModeViewModel viewModel = ViewModelProviders.of(this).get(EncryptModeViewModel.class); viewModel.getSignKeyLiveData(requireContext()).observe(this, mSignKeySpinner::setData); - viewModel.getEncryptRecipientLiveData(requireContext()).observe(this, this::onLoadEncryptRecipients); + viewModel.getEncryptRecipientLiveData(requireContext()).observe(this, (keyUnifiedData) -> { + ArrayList simpleChips = new ArrayList<>(); + for (EncryptRecipientChip chip : keyUnifiedData) { + simpleChips.add(new SimpleChip(chip.keyInfo.master_key_id(), chip.keyInfo.name(), chip.keyInfo.email(), chip.keyInfo.user_id_list())); + } + mEncryptKeyView.setData(simpleChips); + }); // preselect keys given, from state or arguments if (savedInstanceState == null) { @@ -154,14 +148,9 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { } } - private void onLoadEncryptRecipients(List keyInfoChips) { - SimpleChipDropdownAdapter chipDropdownAdapter = new SimpleChipDropdownAdapter(requireContext(), keyInfoChips); - mEncryptKeyView.setChipDropdownAdapter(chipDropdownAdapter); - } - public static class EncryptModeViewModel extends ViewModel { private LiveData> signKeyLiveData; - private LiveData> encryptRecipientLiveData; + private LiveData> encryptRecipientLiveData; LiveData> getSignKeyLiveData(Context context) { if (signKeyLiveData == null) { @@ -173,14 +162,15 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { return signKeyLiveData; } - LiveData> getEncryptRecipientLiveData(Context context) { + LiveData> getEncryptRecipientLiveData(Context context) { if (encryptRecipientLiveData == null) { encryptRecipientLiveData = new GenericLiveData<>(context, () -> { KeyRepository keyRepository = KeyRepository.create(context); List keyInfos = keyRepository.getAllUnifiedKeyInfo(); - ArrayList result = new ArrayList<>(); + ArrayList result = new ArrayList<>(); for (UnifiedKeyInfo keyInfo : keyInfos) { - result.add(new SimpleChip(keyInfo.master_key_id(), keyInfo.name(), keyInfo.email(), keyInfo.user_id_list())); + EncryptRecipientChip chip = EncryptRecipientChipsInput.chipFromUnifiedKeyInfo(keyInfo); + result.add(chip); } return result; }); @@ -205,22 +195,16 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { if (encryptionKeyIds != null) { for (long preselectedId : encryptionKeyIds) { - try { - CanonicalizedPublicKeyRing ring = - keyRepository.getCanonicalizedPublicKeyRing(preselectedId); - SimpleChip infooo = new SimpleChip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo", null); - mEncryptKeyView.addChip(infooo); - } catch (NotFoundException e) { - Timber.e(e, "key not found for encryption!"); - Notify.create(getActivity(), getString(R.string.error_preselect_encrypt_key, - KeyFormattingUtils.beautifyKeyId(preselectedId)), - Style.ERROR).show(); - } + UnifiedKeyInfo keyInfo = keyRepository.getUnifiedKeyInfo(preselectedId); + EncryptRecipientChip recipientChip = EncryptRecipientChipsInput.chipFromUnifiedKeyInfo(keyInfo); + // mEncryptKeyView.addChip(recipientChip); + // EncryptRecipientChip infooo = + // new EncryptRecipientChip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo", null); + // mEncryptKeyView.addChip(infooo); } // This is to work-around a rendering bug in TokenCompleteTextView mEncryptKeyView.requestFocus(); } - } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java new file mode 100644 index 000000000..ea4c1bd47 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -0,0 +1,53 @@ +package org.sufficientlysecure.keychain.ui.chips; + + +import java.util.List; + +import android.content.Context; +import android.util.AttributeSet; + +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; + + +public class EncryptRecipientChipsInput extends ChipsInput { + public EncryptRecipientChipsInput(Context context) { + super(context); + init(); + } + + public EncryptRecipientChipsInput(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { +// ChipsAdapter chipsAdapter = new SimpleChipsAdapter(getContext(), this); +// setChipsAdapter(chipsAdapter); + } + + public void setData(List keyInfoChips) { +// SimpleChipDropdownAdapter chipDropdownAdapter = new SimpleChipDropdownAdapter(getContext(), simpleChips); +// setChipDropdownAdapter(chipDropdownAdapter); + } + + public static class EncryptRecipientChip implements FilterableItem { + public final UnifiedKeyInfo keyInfo; + + EncryptRecipientChip(UnifiedKeyInfo keyInfo) { + this.keyInfo = keyInfo; + } + + @Override + public boolean isKeptForConstraint(CharSequence constraint) { + String uidList = keyInfo.user_id_list(); + return uidList == null || uidList.contains(constraint); + } + } + + public static EncryptRecipientChip chipFromUnifiedKeyInfo(UnifiedKeyInfo keyInfo) { + return new EncryptRecipientChip(keyInfo); + } +} diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index 1ff410f43..7f4b505b7 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -38,7 +38,7 @@ - diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java index cfa962a44..5320a397b 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -8,8 +8,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.text.Editable; @@ -28,44 +26,34 @@ import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager; import com.pchmn.materialchips.RecyclerItemClickListener.OnItemClickListener; import com.pchmn.materialchips.adapter.ChipsAdapter; import com.pchmn.materialchips.adapter.FilterableAdapter; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.model.SimpleChip; +import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; import com.pchmn.materialchips.util.ActivityUtil; -import com.pchmn.materialchips.util.MyWindowCallback; +import com.pchmn.materialchips.util.ClickOutsideCallback; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.ChipsInputEditText; -import com.pchmn.materialchips.views.DetailedChipView; import com.pchmn.materialchips.views.DropdownListView; import com.pchmn.materialchips.views.ScrollViewMaxHeight; -public class ChipsInput extends ScrollViewMaxHeight { - // context +public abstract class ChipsInput extends ScrollViewMaxHeight { private Context mContext; - private ChipsAdapter mChipsAdapter; + // attributes - private static final int NONE = -1; private String mHint; private ColorStateList mHintColor; private ColorStateList mTextColor; private int mMaxRows = 2; - private ColorStateList mChipLabelColor; - private boolean mChipDeletable = false; - private Drawable mChipDeleteIcon; - private ColorStateList mChipDeleteIconColor; - private ColorStateList mChipBackgroundColor; private boolean mShowChipDetailed = true; - private ColorStateList mChipDetailedTextColor; - private ColorStateList mChipDetailedDeleteIconColor; - private ColorStateList mChipDetailedBackgroundColor; - // chips listener - private List mChipsListenerList = new ArrayList<>(); - // chip list - private DropdownListView mDropdownListView; - // chip validator - private ChipValidator mChipValidator; + + private List> mChipsListenerList = new ArrayList<>(); + private ChipValidator mChipValidator; + + private ChipsAdapter chipsAdapter; + private RecyclerView chipsRecyclerView; + private ChipsInputEditText chipsInputEditText; + + private ChipDropdownAdapter filterableAdapter; private ViewGroup filterableListLayout; - private ChipsInputEditText mEditText; - private ChipDropdownAdapter filterableAdapter; + private DropdownListView mDropdownListView; public ChipsInput(Context context) { super(context); @@ -88,7 +76,7 @@ public class ChipsInput extends ScrollViewMaxHeight { // inflate filterableListLayout View rootView = inflate(getContext(), R.layout.chips_input, this); - RecyclerView recyclerView = rootView.findViewById(R.id.chips_recycler); + chipsRecyclerView = rootView.findViewById(R.id.chips_recycler); initEditText(); @@ -107,91 +95,82 @@ public class ChipsInput extends ScrollViewMaxHeight { mMaxRows = a.getInteger(R.styleable.ChipsInput_maxRows, 2); setMaxHeight(ViewUtil.dpToPx((40 * mMaxRows) + 8)); //setVerticalScrollBarEnabled(true); - // chip label color - mChipLabelColor = a.getColorStateList(R.styleable.ChipsInput_chip_labelColor); - // chip delete icon - mChipDeletable = a.getBoolean(R.styleable.ChipsInput_chip_deletable, false); - mChipDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_deleteIconColor); - int deleteIconId = a.getResourceId(R.styleable.ChipsInput_chip_deleteIcon, NONE); - if (deleteIconId != NONE) - mChipDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId); - // chip background color - mChipBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_backgroundColor); - // show chip detailed mShowChipDetailed = a.getBoolean(R.styleable.ChipsInput_showChipDetailed, true); // chip detailed text color - mChipDetailedTextColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_textColor); - mChipDetailedBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_backgroundColor); - mChipDetailedDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_deleteIconColor); } finally { a.recycle(); } } - // adapter - mChipsAdapter = new ChipsAdapter(mContext, this, mEditText, recyclerView); ChipsLayoutManager chipsLayoutManager = ChipsLayoutManager.newBuilder(mContext) .setOrientation(ChipsLayoutManager.HORIZONTAL) .build(); - recyclerView.setLayoutManager(chipsLayoutManager); - recyclerView.setNestedScrollingEnabled(false); - recyclerView.setAdapter(mChipsAdapter); + chipsRecyclerView.setLayoutManager(chipsLayoutManager); + chipsRecyclerView.setNestedScrollingEnabled(false); - // set window callback - // will hide DetailedOpenView and hide keyboard on touch outside + setupClickOutsideCallback(); + } + + public void setChipsAdapter(ChipsAdapter chipsAdapter) { + this.chipsAdapter = chipsAdapter; + chipsRecyclerView.setAdapter(chipsAdapter); + } + + private void setupClickOutsideCallback() { Activity activity = ActivityUtil.scanForActivity(mContext); - if (activity == null) + if (activity == null) { throw new ClassCastException("android.view.Context cannot be cast to android.app.Activity"); + } - android.view.Window.Callback mCallBack = (activity).getWindow().getCallback(); - activity.getWindow().setCallback(new MyWindowCallback(mCallBack, activity)); + android.view.Window.Callback originalWindowCallback = (activity).getWindow().getCallback(); + activity.getWindow().setCallback(new ClickOutsideCallback(originalWindowCallback, activity)); } private void initEditText() { - mEditText = new ChipsInputEditText(mContext); + chipsInputEditText = new ChipsInputEditText(mContext); if (mHintColor != null) - mEditText.setHintTextColor(mHintColor); + chipsInputEditText.setHintTextColor(mHintColor); if (mTextColor != null) - mEditText.setTextColor(mTextColor); + chipsInputEditText.setTextColor(mTextColor); - mEditText.setLayoutParams(new RelativeLayout.LayoutParams( + chipsInputEditText.setLayoutParams(new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - mEditText.setHint(mHint); - mEditText.setBackgroundResource(android.R.color.transparent); + chipsInputEditText.setHint(mHint); + chipsInputEditText.setBackgroundResource(android.R.color.transparent); // prevent fullscreen on landscape - mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); - mEditText.setPrivateImeOptions("nm"); + chipsInputEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); + chipsInputEditText.setPrivateImeOptions("nm"); // no suggestion - mEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + chipsInputEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); // handle back space - mEditText.setOnKeyListener(new View.OnKeyListener() { + chipsInputEditText.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { // backspace if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { // remove last chip - if (mEditText.getText().toString().length() == 0) - mChipsAdapter.removeLastChip(); + if (chipsInputEditText.getText().toString().length() == 0) + chipsAdapter.removeLastChip(); } return false; } }); - mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + chipsInputEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if ((actionId == EditorInfo.IME_ACTION_DONE) || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { - ChipsInput.this.onActionDone(mEditText.getText().toString()); + ChipsInput.this.onActionDone(chipsInputEditText.getText().toString()); } return false; } }); // text changed - mEditText.addTextChangedListener(new TextWatcher() { + chipsInputEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -208,80 +187,28 @@ public class ChipsInput extends ScrollViewMaxHeight { }); } - public void addChips(List chipList) { - mChipsAdapter.addChipsProgrammatically(chipList); - } - - public void addChip(ChipInterface chip) { - mChipsAdapter.addChip(chip); - } - - public void addChip(Object id, String label, String info, String filterString) { - SimpleChip chip = new SimpleChip(id, label, info, filterString); - mChipsAdapter.addChip(chip); - } - - public void addChip(String label, String info) { - SimpleChip chip = new SimpleChip(label, info); - mChipsAdapter.addChip(chip); - } - - public void removeChip(ChipInterface chip) { - mChipsAdapter.removeChip(chip); - } - - public void removeChipById(Object id) { - mChipsAdapter.removeChipById(id); - } - - public void removeChipByLabel(String label) { - mChipsAdapter.removeChipByLabel(label); - } - - public void removeChipByInfo(String info) { - mChipsAdapter.removeChipByInfo(info); - } - - public ChipView getChipView() { - int padding = ViewUtil.dpToPx(4); - ChipView chipView = new ChipView.Builder(mContext) - .labelColor(mChipLabelColor) - .deletable(mChipDeletable) - .deleteIcon(mChipDeleteIcon) - .deleteIconColor(mChipDeleteIconColor) - .backgroundColor(mChipBackgroundColor) - .build(); - - chipView.setPadding(padding, padding, padding, padding); - - return chipView; + public void addChip(T chip) { + chipsAdapter.addChip(chip); } public ChipsInputEditText getEditText() { - return mChipsAdapter.getmEditText(); + return chipsInputEditText; } - public DetailedChipView getDetailedChipView(ChipInterface chip) { - return new DetailedChipView.Builder(mContext) - .chip(chip) - .textColor(mChipDetailedTextColor) - .backgroundColor(mChipDetailedBackgroundColor) - .deleteIconColor(mChipDetailedDeleteIconColor) - .build(); - } - - public void addChipsListener(ChipsListener chipsListener) { + public void addChipsListener(ChipsListener chipsListener) { mChipsListenerList.add(chipsListener); } - public void onChipAdded(ChipInterface chip, int size) { - for (ChipsListener chipsListener : mChipsListenerList) { + public void onChipAdded(T chip, int size) { + filterableAdapter.hideItem(chip); + for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onChipAdded(chip, size); } } - public void onChipRemoved(ChipInterface chip, int size) { - for (ChipsListener chipsListener : mChipsListenerList) { + public void onChipRemoved(T chip, int size) { + filterableAdapter.unhideItem(chip); + for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onChipRemoved(chip, size); } } @@ -290,6 +217,9 @@ public class ChipsInput extends ScrollViewMaxHeight { for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onTextChanged(text); } + + mDropdownListView.getRecyclerView().scrollToPosition(0); + // show filterable list if (mDropdownListView != null) { if (text.length() > 0) { @@ -317,10 +247,11 @@ public class ChipsInput extends ScrollViewMaxHeight { for (ChipsListener chipsListener : mChipsListenerList) { chipsListener.onActionDone(text); } + mDropdownListView.getRecyclerView().scrollToPosition(0); } - public List getSelectedChipList() { - return mChipsAdapter.getChipList(); + public List getSelectedChipList() { + return chipsAdapter.getChipList(); } public String getHint() { @@ -344,26 +275,6 @@ public class ChipsInput extends ScrollViewMaxHeight { return this; } - public void setChipLabelColor(ColorStateList mLabelColor) { - this.mChipLabelColor = mLabelColor; - } - - public void setChipDeletable(boolean mDeletable) { - this.mChipDeletable = mDeletable; - } - - public void setChipDeleteIcon(Drawable mDeleteIcon) { - this.mChipDeleteIcon = mDeleteIcon; - } - - public void setChipDeleteIconColor(ColorStateList mDeleteIconColor) { - this.mChipDeleteIconColor = mDeleteIconColor; - } - - public void setChipBackgroundColor(ColorStateList mBackgroundColor) { - this.mChipBackgroundColor = mBackgroundColor; - } - public ChipsInput setShowChipDetailed(boolean mShowChipDetailed) { this.mShowChipDetailed = mShowChipDetailed; return this; @@ -373,30 +284,22 @@ public class ChipsInput extends ScrollViewMaxHeight { return mShowChipDetailed; } - public void setChipDetailedTextColor(ColorStateList mChipDetailedTextColor) { - this.mChipDetailedTextColor = mChipDetailedTextColor; - } - - public void setChipDetailedDeleteIconColor(ColorStateList mChipDetailedDeleteIconColor) { - this.mChipDetailedDeleteIconColor = mChipDetailedDeleteIconColor; - } - - public void setChipDetailedBackgroundColor(ColorStateList mChipDetailedBackgroundColor) { - this.mChipDetailedBackgroundColor = mChipDetailedBackgroundColor; - } - public void setFilterableListLayout(ViewGroup layout) { this.filterableListLayout = layout; } - public abstract static class ChipDropdownAdapter + public RecyclerView getChipsRecyclerView() { + return chipsRecyclerView; + } + + public abstract static class ChipDropdownAdapter extends FilterableAdapter { public ChipDropdownAdapter(List itemList) { super(itemList); } } - public void setChipDropdownAdapter(final ChipDropdownAdapter filterableAdapter) { + public void setChipDropdownAdapter(final ChipDropdownAdapter filterableAdapter) { this.filterableAdapter = filterableAdapter; if (filterableListLayout != null) { mDropdownListView = new DropdownListView(mContext, filterableListLayout); @@ -404,57 +307,39 @@ public class ChipsInput extends ScrollViewMaxHeight { mDropdownListView = new DropdownListView(mContext, this); } mDropdownListView.build(filterableAdapter); - mChipsAdapter.setFilterableListView(mDropdownListView); + chipsAdapter.setFilterableListView(mDropdownListView); mDropdownListView.getRecyclerView().addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new OnItemClickListener() { @Override public void onItemClick(View view, int position) { - ChipInterface item = filterableAdapter.getItem(position); - addChip(item); + T item = filterableAdapter.getItem(position); + chipsAdapter.addChip(item); } })); - - addChipsListener(new ChipsInput.ChipsListener() { - @Override - public void onChipAdded(ChipInterface chip, int newSize) { - filterableAdapter.hideItem((T) chip); - } - - @Override - public void onChipRemoved(ChipInterface chip, int newSize) { - filterableAdapter.unhideItem((T) chip); - } - - @Override - public void onTextChanged(CharSequence text) { - mDropdownListView.getRecyclerView().scrollToPosition(0); - } - - @Override - public void onActionDone(CharSequence text) { - mDropdownListView.getRecyclerView().scrollToPosition(0); - } - }); } - public ChipValidator getChipValidator() { + public ChipValidator getChipValidator() { return mChipValidator; } - public void setChipValidator(ChipValidator mChipValidator) { + public void setChipValidator(ChipValidator mChipValidator) { this.mChipValidator = mChipValidator; } - public interface ChipsListener { - void onChipAdded(ChipInterface chip, int newSize); - - void onChipRemoved(ChipInterface chip, int newSize); - + public interface ChipsListener { + void onChipAdded(T chip, int newSize); + void onChipRemoved(T chip, int newSize); void onTextChanged(CharSequence text); - void onActionDone(CharSequence text); } - public interface ChipValidator { - boolean areEquals(ChipInterface chip1, ChipInterface chip2); + public static abstract class SimpleChipsListener implements ChipsListener { + public void onChipAdded(T chip, int newSize) { } + public void onChipRemoved(T chip, int newSize) { } + public void onTextChanged(CharSequence text) { } + public void onActionDone(CharSequence text) { } + } + + public interface ChipValidator { + boolean areEquals(T chip1, T chip2); } } diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java index f20462257..1770a2b77 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -1,8 +1,14 @@ package com.pchmn.materialchips.adapter; + +import java.util.ArrayList; +import java.util.List; + import android.content.Context; import android.os.Build; +import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -11,105 +17,49 @@ import android.widget.RelativeLayout; import com.pchmn.materialchips.ChipView; import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DetailedChipView; import com.pchmn.materialchips.views.DropdownListView; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - - -public class ChipsAdapter extends RecyclerView.Adapter { +public abstract class ChipsAdapter + extends RecyclerView.Adapter { private static final int TYPE_EDIT_TEXT = 0; private static final int TYPE_ITEM = 1; - private Context mContext; - private ChipsInput mChipsInput; - private List mChipList = new ArrayList<>(); - private String mHintLabel; - private ChipsInputEditText mEditText; + protected Context context; - private RecyclerView mRecycler; + private ChipsInput chipsInput; + private List chipList = new ArrayList<>(); - public ChipsAdapter(Context context, ChipsInput chipsInput, RecyclerView recycler) { - mContext = context; - mChipsInput = chipsInput; - mRecycler = recycler; - mHintLabel = mChipsInput.getHint(); - } + private ChipsInputEditText editText; + private String hintLabel; - public ChipsAdapter(Context mContext, ChipsInput chipsInput, ChipsInputEditText mEditText, RecyclerView mRecyclerView) { - this(mContext, chipsInput, mRecyclerView); - this.mEditText = mEditText; - } + private RecyclerView chipsRecycler; - private class ItemViewHolder extends RecyclerView.ViewHolder { + public ChipsAdapter(Context context, ChipsInput chipsInput) { + this.chipsInput = chipsInput; + this.chipsRecycler = chipsInput.getChipsRecyclerView(); + this.editText = chipsInput.getEditText(); + this.context = context; - private final ChipView chipView; - - ItemViewHolder(View view) { - super(view); - chipView = (ChipView) view; - } - - } - private class EditTextViewHolder extends RecyclerView.ViewHolder { - - private final EditText editText; - - EditTextViewHolder(View view) { - super(view); - editText = (EditText) view; - } - - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == TYPE_EDIT_TEXT) { - return new EditTextViewHolder(mEditText); - } else { - return new ItemViewHolder(mChipsInput.getChipView()); - } - - } - - @Override - public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { - // edit text - if (position == mChipList.size()) { - if (mChipList.size() == 0) { - mEditText.setHint(mHintLabel); - } - - // auto fit edit text - autofitEditText(); - } - // chip - else if (getItemCount() > 1) { - ItemViewHolder itemViewHolder = (ItemViewHolder) holder; - itemViewHolder.chipView.inflate(getItem(position)); - // handle click - handleClickOnEditText(itemViewHolder.chipView, position); - } + this.hintLabel = chipsInput.getHint(); } @Override public int getItemCount() { - return mChipList.size() + 1; + return chipList.size() + 1; } - private ChipInterface getItem(int position) { - return mChipList.get(position); + protected T getItem(int position) { + return chipList.get(position); } @Override public int getItemViewType(int position) { - if (position == mChipList.size()) { + if (position == chipList.size()) { return TYPE_EDIT_TEXT; } @@ -118,44 +68,44 @@ public class ChipsAdapter extends RecyclerView.Adapter @Override public long getItemId(int position) { - return mChipList.get(position).hashCode(); + return chipList.get(position).hashCode(); } private void autofitEditText() { // min width of edit text = 50 dp - ViewGroup.LayoutParams params = mEditText.getLayoutParams(); + ViewGroup.LayoutParams params = editText.getLayoutParams(); params.width = ViewUtil.dpToPx(50); - mEditText.setLayoutParams(params); + editText.setLayoutParams(params); // listen to change in the tree - mEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + editText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // get right of recycler and left of edit text - int right = mRecycler.getRight(); - int left = mEditText.getLeft(); + int right = chipsRecycler.getRight(); + int left = editText.getLeft(); // edit text will fill the space - ViewGroup.LayoutParams params = mEditText.getLayoutParams(); + ViewGroup.LayoutParams params = editText.getLayoutParams(); params.width = right - left - ViewUtil.dpToPx(8); - mEditText.setLayoutParams(params); + editText.setLayoutParams(params); // request focus - mEditText.requestFocus(); + editText.requestFocus(); // remove the listener: if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - mEditText.getViewTreeObserver().removeGlobalOnLayoutListener(this); + editText.getViewTreeObserver().removeGlobalOnLayoutListener(this); } else { - mEditText.getViewTreeObserver().removeOnGlobalLayoutListener(this); + editText.getViewTreeObserver().removeOnGlobalLayoutListener(this); } } }); } - private void handleClickOnEditText(ChipView chipView, final int position) { + public void handleClickOnEditText(ChipView chipView, final int position) { // delete chip chipView.setOnDeleteClicked(new View.OnClickListener() { @Override @@ -165,7 +115,7 @@ public class ChipsAdapter extends RecyclerView.Adapter }); // show detailed chip - if (mChipsInput.isShowChipDetailed()) { + if (chipsInput.isShowChipDetailed()) { chipView.setOnChipClicked(new View.OnClickListener() { @Override public void onClick(View v) { @@ -173,7 +123,7 @@ public class ChipsAdapter extends RecyclerView.Adapter int[] coord = new int[2]; v.getLocationInWindow(coord); - final DetailedChipView detailedChipView = mChipsInput.getDetailedChipView(getItem(position)); + final DetailedChipView detailedChipView = getDetailedChipView(getItem(position)); setDetailedChipViewPosition(detailedChipView, coord); // delete button @@ -189,10 +139,12 @@ public class ChipsAdapter extends RecyclerView.Adapter } } + public abstract DetailedChipView getDetailedChipView(T chip); + private void setDetailedChipViewPosition(DetailedChipView detailedChipView, int[] coord) { // window width - ViewGroup rootView = (ViewGroup) mRecycler.getRootView(); - int windowWidth = ViewUtil.getWindowWidth(mContext); + ViewGroup rootView = (ViewGroup) chipsRecycler.getRootView(); + int windowWidth = ViewUtil.getWindowWidth(context); // chip size RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( @@ -226,150 +178,123 @@ public class ChipsAdapter extends RecyclerView.Adapter } public void setFilterableListView(DropdownListView dropdownListView) { - if (mEditText != null) { - mEditText.setFilterableListView(dropdownListView); + if (editText != null) { + editText.setFilterableListView(dropdownListView); } } - public void addChipsProgrammatically(List chipList) { + public void addChipsProgrammatically(List chipList) { if (chipList != null) { if (chipList.size() > 0) { int chipsBeforeAdding = getItemCount(); - for (ChipInterface chip : chipList) { - mChipList.add(chip); - mChipsInput.onChipAdded(chip, getItemCount()); + for (T chip : chipList) { + this.chipList.add(chip); + chipsInput.onChipAdded(chip, getItemCount()); } // hide hint - mEditText.setHint(null); + editText.setHint(null); // reset text - mEditText.setText(null); + editText.setText(null); notifyItemRangeChanged(chipsBeforeAdding, chipList.size()); } } } - public void addChip(ChipInterface chip) { - if (!listContains(mChipList, chip)) { - mChipList.add(chip); + public void addChip(T chip) { + if (!listContains(chipList, chip)) { + chipList.add(chip); // notify listener - mChipsInput.onChipAdded(chip, mChipList.size()); + chipsInput.onChipAdded(chip, chipList.size()); // hide hint - mEditText.setHint(null); + editText.setHint(null); // reset text - mEditText.setText(null); + editText.setText(null); // refresh data - notifyItemInserted(mChipList.size()); + notifyItemInserted(chipList.size()); } } - public void removeChip(ChipInterface chip) { - int position = mChipList.indexOf(chip); - mChipList.remove(position); + public void removeChip(T chip) { + int position = chipList.indexOf(chip); + chipList.remove(position); // notify listener notifyItemRangeChanged(position, getItemCount()); - mChipsInput.onChipRemoved(chip, mChipList.size()); + chipsInput.onChipRemoved(chip, chipList.size()); // if 0 chip - if (mChipList.size() == 0) { - mEditText.setHint(mHintLabel); + if (chipList.size() == 0) { + editText.setHint(hintLabel); } // refresh data notifyDataSetChanged(); } public void removeChip(int position) { - ChipInterface chip = mChipList.get(position); + T chip = chipList.get(position); // remove contact - mChipList.remove(position); + chipList.remove(position); // notify listener - mChipsInput.onChipRemoved(chip, mChipList.size()); + chipsInput.onChipRemoved(chip, chipList.size()); // if 0 chip - if (mChipList.size() == 0) { - mEditText.setHint(mHintLabel); - } - // refresh data - notifyDataSetChanged(); - } - - public void removeChipById(Object id) { - for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { - ChipInterface chip = iter.next(); - if (chip.getId() != null && chip.getId().equals(id)) { - // remove chip - iter.remove(); - // notify listener - mChipsInput.onChipRemoved(chip, mChipList.size()); - } - } - // if 0 chip - if (mChipList.size() == 0) { - mEditText.setHint(mHintLabel); - } - // refresh data - notifyDataSetChanged(); - } - - public void removeChipByLabel(String label) { - for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { - ChipInterface chip = iter.next(); - if (chip.getLabel().equals(label)) { - // remove chip - iter.remove(); - // notify listener - mChipsInput.onChipRemoved(chip, mChipList.size()); - } - } - // if 0 chip - if (mChipList.size() == 0) { - mEditText.setHint(mHintLabel); - } - // refresh data - notifyDataSetChanged(); - } - - public void removeChipByInfo(String info) { - for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { - ChipInterface chip = iter.next(); - if (chip.getInfo() != null && chip.getInfo().equals(info)) { - // remove chip - iter.remove(); - // notify listener - mChipsInput.onChipRemoved(chip, mChipList.size()); - } - } - // if 0 chip - if (mChipList.size() == 0) { - mEditText.setHint(mHintLabel); + if (chipList.size() == 0) { + editText.setHint(hintLabel); } // refresh data notifyDataSetChanged(); } public void removeLastChip() { - if (mChipList.size() > 0) { - removeChip(mChipList.get(mChipList.size() - 1)); + if (chipList.size() > 0) { + removeChip(chipList.get(chipList.size() - 1)); } } - public List getChipList() { - return mChipList; + public List getChipList() { + return chipList; } - private boolean listContains(List contactList, ChipInterface chip) { - if (mChipsInput.getChipValidator() != null) { - for (ChipInterface item : contactList) { - if (mChipsInput.getChipValidator().areEquals(item, chip)) { - return true; - } - } + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == TYPE_EDIT_TEXT) { + return new EditTextViewHolder(editText); } else { - for (ChipInterface item : contactList) { - if (chip.getId() != null && chip.getId().equals(item.getId())) { - return true; - } - if (chip.getLabel().equals(item.getLabel())) { + return onCreateChipViewHolder(parent, viewType); + } + } + + public abstract ViewHolder onCreateChipViewHolder(ViewGroup parent, int viewType); + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (position == chipList.size()) { + if (chipList.size() == 0) { + editText.setHint(hintLabel); + } + + autofitEditText(); + } else if (getItemCount() > 1) { + onBindChipViewHolder((VH) holder, position); + } + } + + public abstract void onBindChipViewHolder(VH holder, int position); + + protected class EditTextViewHolder extends RecyclerView.ViewHolder { + private final EditText editText; + + EditTextViewHolder(View view) { + super(view); + editText = (EditText) view; + } + } + + private boolean listContains(List contactList, T chip) { + if (chipsInput.getChipValidator() != null) { + for (T item : contactList) { + if (chipsInput.getChipValidator().areEquals(item, chip)) { return true; } } @@ -377,8 +302,4 @@ public class ChipsAdapter extends RecyclerView.Adapter return false; } - - public ChipsInputEditText getmEditText() { - return mEditText; - } } diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChip.java similarity index 92% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java rename to extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChip.java index acd740b17..9e45eee83 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/SimpleChip.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChip.java @@ -1,14 +1,14 @@ -package com.pchmn.materialchips.model; +package com.pchmn.materialchips.simple; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; +import com.pchmn.materialchips.model.ChipInterface; public class SimpleChip implements ChipInterface { - private Object id; private String label; private String info; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipDropdownAdapter.java similarity index 83% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java rename to extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipDropdownAdapter.java index cdf278088..7601d4fe2 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/SimpleChipDropdownAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipDropdownAdapter.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.adapter; +package com.pchmn.materialchips.simple; import java.util.List; @@ -13,9 +13,7 @@ import android.widget.TextView; import com.pchmn.materialchips.ChipsInput.ChipDropdownAdapter; import com.pchmn.materialchips.R; -import com.pchmn.materialchips.adapter.SimpleChipDropdownAdapter.ItemViewHolder; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.model.SimpleChip; +import com.pchmn.materialchips.simple.SimpleChipDropdownAdapter.ItemViewHolder; public class SimpleChipDropdownAdapter extends ChipDropdownAdapter { @@ -27,7 +25,7 @@ public class SimpleChipDropdownAdapter extends ChipDropdownAdapter { + public SimpleChipsAdapter(Context context, ChipsInput chipsInput) { + super(context, chipsInput); + } + + class ItemViewHolder extends RecyclerView.ViewHolder { + private final ChipView chipView; + + ItemViewHolder(View view) { + super(view); + chipView = (ChipView) view; + } + } + + @Override + public ItemViewHolder onCreateChipViewHolder(ViewGroup parent, int viewType) { + int padding = ViewUtil.dpToPx(4); + ChipView chipView = new ChipView.Builder(context) + // .labelColor(mChipLabelColor) + // .deletable(mChipDeletable) + // .deleteIcon(mChipDeleteIcon) + // .deleteIconColor(mChipDeleteIconColor) + .build(); + chipView.setPadding(padding, padding, padding, padding); + + return new ItemViewHolder(chipView); + } + + @Override + public void onBindChipViewHolder(ItemViewHolder holder, int position) { + holder.chipView.inflate(getItem(position)); + handleClickOnEditText(holder.chipView, position); + } + + @Override + public DetailedChipView getDetailedChipView(SimpleChip chip) { + return new DetailedChipView.Builder(context) + .chip(chip) + .build(); + } + +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java new file mode 100644 index 000000000..735636ee5 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java @@ -0,0 +1,32 @@ +package com.pchmn.materialchips.simple; + + +import java.util.List; + +import android.content.Context; +import android.util.AttributeSet; + +import com.pchmn.materialchips.ChipsInput; + + +public class SimpleChipsInput extends ChipsInput { + public SimpleChipsInput(Context context) { + super(context); + init(); + } + + public SimpleChipsInput(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + SimpleChipsAdapter chipsAdapter = new SimpleChipsAdapter(getContext(), this); + setChipsAdapter(chipsAdapter); + } + + public void setData(List simpleChips) { + SimpleChipDropdownAdapter chipDropdownAdapter = new SimpleChipDropdownAdapter(getContext(), simpleChips); + setChipDropdownAdapter(chipDropdownAdapter); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ClickOutsideCallback.java similarity index 91% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java rename to extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ClickOutsideCallback.java index b6e819c6e..367e57d57 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ClickOutsideCallback.java @@ -11,10 +11,10 @@ import android.view.inputmethod.InputMethodManager; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DetailedChipView; -public class MyWindowCallback extends DelegateWindowCallback { +public class ClickOutsideCallback extends DelegateWindowCallback { private Activity activity; - public MyWindowCallback(Window.Callback delegateCallback, Activity activity) { + public ClickOutsideCallback(Window.Callback delegateCallback, Activity activity) { super(delegateCallback); this.activity = activity; } diff --git a/extern/MaterialChipsInput/src/main/res/values/attrs.xml b/extern/MaterialChipsInput/src/main/res/values/attrs.xml index 7c49f83a0..189c0eec7 100644 --- a/extern/MaterialChipsInput/src/main/res/values/attrs.xml +++ b/extern/MaterialChipsInput/src/main/res/values/attrs.xml @@ -17,13 +17,7 @@ - - - - - - From 76e624f12fd8ab4ac8639dd9619f3db6a44b9813 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 20:12:24 +0200 Subject: [PATCH 098/124] show partial list of keys if an error is thrown --- .../keyimport/processing/ImportKeysListLoader.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java index 0e5b50168..a53e82118 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/processing/ImportKeysListLoader.java @@ -123,11 +123,13 @@ public class ImportKeysListLoader } } catch (IOException e) { Timber.e(e, "IOException on parsing key file! Return NoValidKeysException!"); - OperationResult.OperationLog log = new OperationResult.OperationLog(); - log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0); - GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log); - mData.clear(); - mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); + if (mData.isEmpty()) { + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0); + GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log); + mData.clear(); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult); + } } } From f8eb6275f55570236e20e13d106c6fe1613af173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 20:07:38 +0200 Subject: [PATCH 099/124] move formatting of key items to new class --- .../ui/adapter/FlexibleKeyDetailsItem.java | 152 +--------------- .../keychain/ui/util/KeyInfoFormatter.java | 167 ++++++++++++++++++ 2 files changed, 176 insertions(+), 143 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java index cf53b113f..16c7a9050 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java @@ -1,13 +1,9 @@ package org.sufficientlysecure.keychain.ui.adapter; +import java.util.Arrays; import java.util.List; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.text.format.DateUtils; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -16,14 +12,12 @@ import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.IFilterable; import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.FlexibleViewHolder; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyDetailsItem.FlexibleKeyItemViewHolder; import org.sufficientlysecure.keychain.ui.adapter.FlexibleKeyItem.FlexibleSectionableKeyItem; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Highlighter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; public class FlexibleKeyDetailsItem extends FlexibleSectionableKeyItem @@ -77,8 +71,6 @@ public class FlexibleKeyDetailsItem extends FlexibleSectionableKeyItem textviews) { + int textColor; + if (keyInfo.is_revoked()) { + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (keyInfo.is_expired()) { + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (!keyInfo.is_secure()) { + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (keyInfo.has_any_secret()) { + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } else { + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + for (TextView textView : textviews) { + textView.setTextColor(textColor); + } + } + + public void formatStatusIcon(ImageView statusIcon) { + if (keyInfo.is_revoked()) { + KeyFormattingUtils.setStatusImage( + context, + statusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + statusIcon.setVisibility(View.VISIBLE); + } else if (keyInfo.is_expired()) { + KeyFormattingUtils.setStatusImage( + context, + statusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + statusIcon.setVisibility(View.VISIBLE); + } else if (!keyInfo.is_secure()) { + KeyFormattingUtils.setStatusImage( + context, + statusIcon, + null, + KeyFormattingUtils.State.INSECURE, + R.color.key_flag_gray + ); + + statusIcon.setVisibility(View.VISIBLE); + } else if (keyInfo.has_any_secret()) { + statusIcon.setVisibility(View.GONE); + } else { + // this is a public key - show if it's verified + if (keyInfo.is_verified()) { + KeyFormattingUtils.setStatusImage( + context, + statusIcon, + KeyFormattingUtils.State.VERIFIED + ); + + statusIcon.setVisibility(View.VISIBLE); + } else { + KeyFormattingUtils.setStatusImage( + context, + statusIcon, + KeyFormattingUtils.State.UNVERIFIED + ); + + statusIcon.setVisibility(View.VISIBLE); + } + } + } + + public void formatTrustIcon(ImageView trustIdIcon) { + if (!keyInfo.has_any_secret() && !keyInfo.autocrypt_package_names().isEmpty()) { + String packageName = keyInfo.autocrypt_package_names().get(0); + Drawable drawable = PackageIconGetter.getInstance(context).getDrawableForPackageName(packageName); + if (drawable != null) { + trustIdIcon.setImageDrawable(drawable); + trustIdIcon.setVisibility(View.VISIBLE); + } else { + trustIdIcon.setVisibility(View.GONE); + } + } else { + trustIdIcon.setVisibility(View.GONE); + } + } + + @NonNull + private String getSecretKeyReadableTime(Context context, SubKey.UnifiedKeyInfo keyInfo) { + long creationMillis = keyInfo.creation() * 1000; + + boolean allowRelativeTimestamp = keyInfo.has_duplicate(); + if (allowRelativeTimestamp) { + long creationAgeMillis = System.currentTimeMillis() - creationMillis; + if (creationAgeMillis < JUST_NOW_THRESHOLD) { + return context.getString(R.string.label_key_created_just_now); + } + } + + String dateTime = DateUtils.formatDateTime(context, + creationMillis, + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + return context.getString(R.string.label_key_created, dateTime); + } +} From 1c7a2b1db1d079f5bd5fb0aad084bbfdd2bc2833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 20:08:19 +0200 Subject: [PATCH 100/124] show status icon etc in chip dropdown --- .../ui/EncryptModeAsymmetricFragment.java | 42 ++++++------- .../EncryptRecipientDropdownAdapter.java | 63 +++++++++++++++++++ .../ui/chips/EncryptRecipientChipsInput.java | 5 +- .../layout/encrypt_asymmetric_fragment.xml | 2 +- 4 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java 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 d1523e9d8..4264575c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -35,9 +35,6 @@ import android.view.ViewGroup; import android.widget.ViewAnimator; import com.pchmn.materialchips.ChipsInput.SimpleChipsListener; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.simple.SimpleChip; -import com.pchmn.materialchips.simple.SimpleChipsInput; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.daos.KeyRepository; @@ -50,13 +47,14 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.util.Passphrase; +import timber.log.Timber; public class EncryptModeAsymmetricFragment extends EncryptModeFragment { KeyRepository keyRepository; private KeySpinner mSignKeySpinner; - private SimpleChipsInput mEncryptKeyView; + private EncryptRecipientChipsInput mEncryptKeyView; public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; @@ -103,16 +101,16 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { mSignKeySpinner.setShowNone(R.string.cert_none); final ViewAnimator vEncryptionIcon = view.findViewById(R.id.result_encryption_icon); - mEncryptKeyView.addChipsListener(new SimpleChipsListener() { + mEncryptKeyView.addChipsListener(new SimpleChipsListener() { @Override - public void onChipAdded(SimpleChip chipInterface, int newSize) { + public void onChipAdded(EncryptRecipientChip chipInterface, int newSize) { if (vEncryptionIcon.getDisplayedChild() != 1) { vEncryptionIcon.setDisplayedChild(1); } } @Override - public void onChipRemoved(SimpleChip chipInterface, int newSize) { + public void onChipRemoved(EncryptRecipientChip chipInterface, int newSize) { int child = newSize == 0 ? 0 : 1; if (vEncryptionIcon.getDisplayedChild() != child) { vEncryptionIcon.setDisplayedChild(child); @@ -129,13 +127,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { EncryptModeViewModel viewModel = ViewModelProviders.of(this).get(EncryptModeViewModel.class); viewModel.getSignKeyLiveData(requireContext()).observe(this, mSignKeySpinner::setData); - viewModel.getEncryptRecipientLiveData(requireContext()).observe(this, (keyUnifiedData) -> { - ArrayList simpleChips = new ArrayList<>(); - for (EncryptRecipientChip chip : keyUnifiedData) { - simpleChips.add(new SimpleChip(chip.keyInfo.master_key_id(), chip.keyInfo.name(), chip.keyInfo.email(), chip.keyInfo.user_id_list())); - } - mEncryptKeyView.setData(simpleChips); - }); + viewModel.getEncryptRecipientLiveData(requireContext()).observe(this, mEncryptKeyView::setData); // preselect keys given, from state or arguments if (savedInstanceState == null) { @@ -196,12 +188,16 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { if (encryptionKeyIds != null) { for (long preselectedId : encryptionKeyIds) { UnifiedKeyInfo keyInfo = keyRepository.getUnifiedKeyInfo(preselectedId); - EncryptRecipientChip recipientChip = EncryptRecipientChipsInput.chipFromUnifiedKeyInfo(keyInfo); - // mEncryptKeyView.addChip(recipientChip); - // EncryptRecipientChip infooo = - // new EncryptRecipientChip(ring.getMasterKeyId(), ring.getPrimaryUserIdWithFallback(), "infooo", null); - // mEncryptKeyView.addChip(infooo); + if (keyInfo == null) { + Timber.e("key not found for encryption!"); + Notify.create(getActivity(), getString(R.string.error_preselect_encrypt_key, + KeyFormattingUtils.beautifyKeyId(preselectedId)), + Style.ERROR).show(); + } else { + mEncryptKeyView.addChip(EncryptRecipientChipsInput.chipFromUnifiedKeyInfo(keyInfo)); + } } + // This is to work-around a rendering bug in TokenCompleteTextView mEncryptKeyView.requestFocus(); } @@ -220,8 +216,8 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public long[] getAsymmetricEncryptionKeyIds() { List keyIds = new ArrayList<>(); - for (ChipInterface chip : mEncryptKeyView.getSelectedChipList()) { - keyIds.add((long) chip.getId()); + for (EncryptRecipientChip chip : mEncryptKeyView.getSelectedChipList()) { + keyIds.add(chip.keyInfo.master_key_id()); } long[] keyIdsArr = new long[keyIds.size()]; @@ -236,8 +232,8 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { @Override public String[] getAsymmetricEncryptionUserIds() { List userIds = new ArrayList<>(); - for (ChipInterface chip : mEncryptKeyView.getSelectedChipList()) { - userIds.add(chip.getInfo()); + for (EncryptRecipientChip chip : mEncryptKeyView.getSelectedChipList()) { + userIds.add(chip.keyInfo.user_id()); } return userIds.toArray(new String[userIds.size()]); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java new file mode 100644 index 000000000..811cd85e0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java @@ -0,0 +1,63 @@ +package org.sufficientlysecure.keychain.ui.adapter; + + +import java.util.List; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.pchmn.materialchips.ChipsInput; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.EncryptRecipientDropdownAdapter.ItemViewHolder; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; + + +public class EncryptRecipientDropdownAdapter extends ChipsInput.ChipDropdownAdapter { + private final LayoutInflater layoutInflater; + + public EncryptRecipientDropdownAdapter(Context context, List keyInfoChips) { + super(keyInfoChips); + + layoutInflater = LayoutInflater.from(context); + } + + class ItemViewHolder extends RecyclerView.ViewHolder { + private final TextView vMainUserId; + private final TextView vMainUserIdRest; + private final TextView vCreationDate; + private final ImageView vStatusIcon; + + ItemViewHolder(View view) { + super(view); + + vMainUserId = itemView.findViewById(R.id.key_list_item_name); + vMainUserIdRest = itemView.findViewById(R.id.key_list_item_email); + vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon); + vCreationDate = itemView.findViewById(R.id.key_list_item_creation); + } + } + + @NonNull + @Override + public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = layoutInflater.inflate(R.layout.key_list_item, parent, false); + return new ItemViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { + EncryptRecipientChip chip = getItem(position); + + KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(layoutInflater.getContext(), chip.keyInfo, null); + keyInfoFormatter.formatUserId(holder.vMainUserId, holder.vMainUserIdRest); + keyInfoFormatter.formatCreationDate(holder.vCreationDate); + keyInfoFormatter.formatStatusIcon(holder.vStatusIcon); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java index ea4c1bd47..2a291791f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -9,6 +9,7 @@ import android.util.AttributeSet; import com.pchmn.materialchips.ChipsInput; import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.adapter.EncryptRecipientDropdownAdapter; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; @@ -29,8 +30,8 @@ public class EncryptRecipientChipsInput extends ChipsInput } public void setData(List keyInfoChips) { -// SimpleChipDropdownAdapter chipDropdownAdapter = new SimpleChipDropdownAdapter(getContext(), simpleChips); -// setChipDropdownAdapter(chipDropdownAdapter); + EncryptRecipientDropdownAdapter chipDropdownAdapter = new EncryptRecipientDropdownAdapter(getContext(), keyInfoChips); + setChipDropdownAdapter(chipDropdownAdapter); } public static class EncryptRecipientChip implements FilterableItem { diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index 7f4b505b7..08bc865cb 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -38,7 +38,7 @@ - Date: Tue, 3 Jul 2018 20:32:27 +0200 Subject: [PATCH 101/124] add preliminary EncryptRecipientChipAdapter --- .../ui/chips/EncryptRecipientChipAdapter.java | 63 +++++++++++++++++++ .../ui/chips/EncryptRecipientChipsInput.java | 4 +- .../com/pchmn/materialchips/ChipsInput.java | 2 +- .../materialchips/adapter/ChipsAdapter.java | 7 --- 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java new file mode 100644 index 000000000..4f7bc6642 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java @@ -0,0 +1,63 @@ +package org.sufficientlysecure.keychain.ui.chips; + + +import android.content.Context; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; + +import com.pchmn.materialchips.ChipView; +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.adapter.ChipsAdapter; +import com.pchmn.materialchips.simple.SimpleChip; +import com.pchmn.materialchips.util.ViewUtil; +import com.pchmn.materialchips.views.DetailedChipView; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipAdapter.ItemViewHolder; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; + + +public class EncryptRecipientChipAdapter extends ChipsAdapter { + public EncryptRecipientChipAdapter(Context context, ChipsInput chipsInput) { + super(context, chipsInput); + } + + @Override + public ItemViewHolder onCreateChipViewHolder(ViewGroup parent, int viewType) { + int padding = ViewUtil.dpToPx(4); + ChipView chipView = new ChipView.Builder(context) + // .labelColor(mChipLabelColor) + // .deletable(mChipDeletable) + // .deleteIcon(mChipDeleteIcon) + // .deleteIconColor(mChipDeleteIconColor) + .build(); + chipView.setPadding(padding, padding, padding, padding); + + return new ItemViewHolder(chipView); + } + + @Override + public void onBindChipViewHolder(ItemViewHolder holder, int position) { + EncryptRecipientChip chip = getItem(position); + holder.chipView.inflate(new SimpleChip(chip.keyInfo.name(), chip.keyInfo.email())); + handleClickOnEditText(holder.chipView, position); + } + + @Override + public DetailedChipView getDetailedChipView(EncryptRecipientChip chip) { + return new DetailedChipView.Builder(context) + .chip(new SimpleChip(chip.keyInfo.name(), chip.keyInfo.email())) + .backgroundColor(ContextCompat.getColorStateList(context, R.color.cardview_light_background)) + .build(); + } + + class ItemViewHolder extends RecyclerView.ViewHolder { + private final ChipView chipView; + + ItemViewHolder(View view) { + super(view); + chipView = (ChipView) view; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java index 2a291791f..41fd161fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -25,8 +25,8 @@ public class EncryptRecipientChipsInput extends ChipsInput } private void init() { -// ChipsAdapter chipsAdapter = new SimpleChipsAdapter(getContext(), this); -// setChipsAdapter(chipsAdapter); + EncryptRecipientChipAdapter chipsAdapter = new EncryptRecipientChipAdapter(getContext(), this); + setChipsAdapter(chipsAdapter); } public void setData(List keyInfoChips) { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java index 5320a397b..961a1fda4 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -307,7 +307,7 @@ public abstract class ChipsInput extends ScrollViewMax mDropdownListView = new DropdownListView(mContext, this); } mDropdownListView.build(filterableAdapter); - chipsAdapter.setFilterableListView(mDropdownListView); + chipsInputEditText.setFilterableListView(mDropdownListView); mDropdownListView.getRecyclerView().addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new OnItemClickListener() { @Override public void onItemClick(View view, int position) { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java index 1770a2b77..df012a5d2 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -21,7 +21,6 @@ import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.ChipsInputEditText; import com.pchmn.materialchips.views.DetailedChipView; -import com.pchmn.materialchips.views.DropdownListView; public abstract class ChipsAdapter @@ -177,12 +176,6 @@ public abstract class ChipsAdapter chipList) { if (chipList != null) { if (chipList.size() > 0) { From 4692f2a39c19839216bd50f0c1edab6daf8e8830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 20:35:42 +0200 Subject: [PATCH 102/124] use KeyInfoFormatter in more places --- ...RemoteSelectAuthenticationKeyActivity.java | 8 +- .../ui/dialog/RemoteSelectIdKeyActivity.java | 7 +- .../keychain/ui/adapter/KeyChoiceAdapter.java | 40 ++++----- .../keychain/ui/util/KeyInfoFormatter.java | 4 + .../ui/widget/KeyChoiceSpinnerAdapter.java | 84 ++----------------- 5 files changed, 35 insertions(+), 108 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java index 7110af55c..9614297d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java @@ -60,6 +60,7 @@ import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; @@ -338,11 +339,8 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { vName.setText(keyInfo.name()); - Context context = vCreation.getContext(); - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation() * 1000, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - vCreation.setText(context.getString(R.string.label_key_created, dateTime)); + KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); + keyInfoFormatter.formatCreationDate(vCreation); vIcon.setImageDrawable(selectionIcon); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java index 76a6dd600..ce5eb16c0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java @@ -71,6 +71,7 @@ import org.sufficientlysecure.keychain.ui.MainActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCallback; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; @@ -523,10 +524,8 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { vName.setText(context.getString(R.string.use_key_no_name)); } - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation() * 1000, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - vCreation.setText(context.getString(R.string.label_key_created, dateTime)); + KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); + keyInfoFormatter.formatCreationDate(vCreation); vIcon.setImageDrawable(selectionIcon); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index b8a6d0701..3e6650f26 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -1,30 +1,30 @@ package org.sufficientlysecure.keychain.ui.adapter; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.CheckBox; +import android.widget.RadioButton; +import android.widget.TextView; +import android.widget.Toast; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceItem; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; -import android.content.Context; -import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.v7.widget.RecyclerView; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.CheckBox; -import android.widget.RadioButton; -import android.widget.TextView; -import android.widget.Toast; - import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.IFlexible; import eu.davidea.viewholders.FlexibleViewHolder; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.ui.adapter.KeyChoiceAdapter.KeyChoiceItem; public class KeyChoiceAdapter extends FlexibleAdapter { @@ -228,16 +228,8 @@ public class KeyChoiceAdapter extends FlexibleAdapter { void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive, boolean isEnabled) { vName.setText(keyInfo.name()); - Context context = vCreation.getContext(); - if (keyInfo.has_any_secret() || keyInfo.has_duplicate()) { - String dateTime = DateUtils.formatDateTime(context, keyInfo.creation() * 1000, - DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | - DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); - vCreation.setText(context.getString(R.string.label_key_created, dateTime)); - vCreation.setVisibility(View.VISIBLE); - } else { - vCreation.setVisibility(View.GONE); - } + KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); + keyInfoFormatter.formatCreationDate(vCreation); switch (choiceMode) { case Mode.IDLE: { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java index d2f3aca5e..de7e97039 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java @@ -58,6 +58,8 @@ public class KeyInfoFormatter { public void greyInvalidKeys(List textviews) { int textColor; + + // Note: order is important! if (keyInfo.is_revoked()) { textColor = ContextCompat.getColor(context, R.color.key_flag_gray); } else if (keyInfo.is_expired()) { @@ -75,6 +77,8 @@ public class KeyInfoFormatter { } public void formatStatusIcon(ImageView statusIcon) { + + // Note: order is important! if (keyInfo.is_revoked()) { KeyFormattingUtils.setStatusImage( context, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java index a27afda34..570336050 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java @@ -1,11 +1,8 @@ package org.sufficientlysecure.keychain.ui.widget; -import java.util.List; - import android.content.Context; import android.support.annotation.StringRes; -import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,9 +12,10 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.ui.util.FormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; + +import java.util.Arrays; +import java.util.List; class KeyChoiceSpinnerAdapter extends BaseAdapter { @@ -137,75 +135,11 @@ class KeyChoiceSpinnerAdapter extends BaseAdapter { } public void bind(Context context, UnifiedKeyInfo keyInfo, boolean enabled) { - { // set name and stuff, common to both key types - if (keyInfo.name() != null) { - mMainUserId.setText(keyInfo.name()); - } else { - mMainUserId.setText(R.string.user_id_no_name); - } - if (keyInfo.email() != null) { - mMainUserIdRest.setText(keyInfo.email()); - mMainUserIdRest.setVisibility(View.VISIBLE); - } else { - mMainUserIdRest.setVisibility(View.GONE); - } - } - - // sort of a hack: if this item isn't enabled, we make it clickable - // to intercept its click events. either way, no listener! - mView.setClickable(!enabled); - - { // set edit button and status, specific by key type - - int textColor; - - // Note: order is important! - if (keyInfo.is_revoked()) { - KeyFormattingUtils - .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); - mStatus.setVisibility(View.VISIBLE); - textColor = context.getResources().getColor(R.color.key_flag_gray); - } else if (keyInfo.is_expired()) { - KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); - mStatus.setVisibility(View.VISIBLE); - textColor = context.getResources().getColor(R.color.key_flag_gray); - } else if (!keyInfo.is_secure()) { - KeyFormattingUtils.setStatusImage(context, mStatus, null, State.INSECURE, R.color.key_flag_gray); - mStatus.setVisibility(View.VISIBLE); - textColor = context.getResources().getColor(R.color.key_flag_gray); - } else if (keyInfo.has_any_secret()) { - mStatus.setVisibility(View.GONE); - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } else { - // this is a public key - show if it's verified - if (keyInfo.is_verified()) { - KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED); - mStatus.setVisibility(View.VISIBLE); - } else { - KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED); - mStatus.setVisibility(View.VISIBLE); - } - textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); - } - - mMainUserId.setTextColor(textColor); - mMainUserIdRest.setTextColor(textColor); - - if (keyInfo.has_duplicate()) { - String dateTime = DateUtils.formatDateTime(context, - keyInfo.creation() * 1000, - DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR - | DateUtils.FORMAT_ABBREV_MONTH); - mCreationDate.setText(context.getString(R.string.label_key_created, - dateTime)); - mCreationDate.setTextColor(textColor); - mCreationDate.setVisibility(View.VISIBLE); - } else { - mCreationDate.setVisibility(View.GONE); - } - } + KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(context, keyInfo, null); + keyInfoFormatter.formatUserId(mMainUserId, mMainUserIdRest); + keyInfoFormatter.formatCreationDate(mCreationDate); + keyInfoFormatter.formatStatusIcon(mStatus); + keyInfoFormatter.greyInvalidKeys(Arrays.asList(mMainUserId, mMainUserIdRest, mCreationDate)); } } } From 449483b4c0d18381f5a909dd99ae2b77c0d84f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 20:38:18 +0200 Subject: [PATCH 103/124] dont show unverified icon --- .../keychain/ui/util/KeyInfoFormatter.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java index de7e97039..f260a00d1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java @@ -120,14 +120,6 @@ public class KeyInfoFormatter { KeyFormattingUtils.State.VERIFIED ); - statusIcon.setVisibility(View.VISIBLE); - } else { - KeyFormattingUtils.setStatusImage( - context, - statusIcon, - KeyFormattingUtils.State.UNVERIFIED - ); - statusIcon.setVisibility(View.VISIBLE); } } From 452f3600db35c34dd129ebb58ea92e7644b44bb4 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 20:43:28 +0200 Subject: [PATCH 104/124] move EncryptRecipientDropdownAdapter to chips package --- .../keychain/ui/chips/EncryptRecipientChipAdapter.java | 2 +- .../keychain/ui/chips/EncryptRecipientChipsInput.java | 1 - .../{adapter => chips}/EncryptRecipientDropdownAdapter.java | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/{adapter => chips}/EncryptRecipientDropdownAdapter.java (89%) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java index 4f7bc6642..524bf4823 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java @@ -19,7 +19,7 @@ import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.Encry public class EncryptRecipientChipAdapter extends ChipsAdapter { - public EncryptRecipientChipAdapter(Context context, ChipsInput chipsInput) { + EncryptRecipientChipAdapter(Context context, ChipsInput chipsInput) { super(context, chipsInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java index 41fd161fc..5b031d268 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -9,7 +9,6 @@ import android.util.AttributeSet; import com.pchmn.materialchips.ChipsInput; import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; -import org.sufficientlysecure.keychain.ui.adapter.EncryptRecipientDropdownAdapter; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java similarity index 89% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java index 811cd85e0..9d8609dca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/EncryptRecipientDropdownAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java @@ -1,4 +1,4 @@ -package org.sufficientlysecure.keychain.ui.adapter; +package org.sufficientlysecure.keychain.ui.chips; import java.util.List; @@ -14,15 +14,15 @@ import android.widget.TextView; import com.pchmn.materialchips.ChipsInput; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.adapter.EncryptRecipientDropdownAdapter.ItemViewHolder; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; +import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientDropdownAdapter.ItemViewHolder; import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; public class EncryptRecipientDropdownAdapter extends ChipsInput.ChipDropdownAdapter { private final LayoutInflater layoutInflater; - public EncryptRecipientDropdownAdapter(Context context, List keyInfoChips) { + EncryptRecipientDropdownAdapter(Context context, List keyInfoChips) { super(keyInfoChips); layoutInflater = LayoutInflater.from(context); From d5a2c6f42d9a32fdb1ac9c01aa98bfcd0c6292c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 21:02:19 +0200 Subject: [PATCH 105/124] correctly remove status icon for unverified keys --- .../sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java index f260a00d1..ab82d025f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java @@ -121,6 +121,8 @@ public class KeyInfoFormatter { ); statusIcon.setVisibility(View.VISIBLE); + } else { + statusIcon.setVisibility(View.GONE); } } } From b350fd62f25488daac4e009e571faf521db606f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 21:03:09 +0200 Subject: [PATCH 106/124] expired before insecure for status icons in key view --- .../keychain/ui/keyview/ViewKeyActivity.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 061796091..3385c2283 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -737,10 +737,10 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements actionEncryptText.setVisibility(View.INVISIBLE); hideFab(); qrCodeLayout.setVisibility(View.GONE); - } else if (!unifiedKeyInfo.is_secure()) { + } else if (unifiedKeyInfo.is_expired()) { statusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.INSECURE, R.color.icons, true); + State.EXPIRED, R.color.icons, true); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_red); @@ -748,10 +748,10 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements actionEncryptText.setVisibility(View.INVISIBLE); hideFab(); qrCodeLayout.setVisibility(View.GONE); - } else if (unifiedKeyInfo.is_expired()) { + } else if (!unifiedKeyInfo.is_secure()) { statusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.EXPIRED, R.color.icons, true); + State.INSECURE, R.color.icons, true); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_red); From 2a7688e3c77eedd7ba23d8194e7a873ac75100a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Tue, 3 Jul 2018 21:21:07 +0200 Subject: [PATCH 107/124] better chips for keys with emails only --- .../ui/chips/EncryptRecipientChipAdapter.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java index 524bf4823..1ed4fb178 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java @@ -13,7 +13,9 @@ import com.pchmn.materialchips.adapter.ChipsAdapter; import com.pchmn.materialchips.simple.SimpleChip; import com.pchmn.materialchips.util.ViewUtil; import com.pchmn.materialchips.views.DetailedChipView; + import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipAdapter.ItemViewHolder; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; @@ -40,14 +42,14 @@ public class EncryptRecipientChipAdapter extends ChipsAdapter Date: Tue, 3 Jul 2018 21:26:17 +0200 Subject: [PATCH 108/124] use UID for now in KeyChoiceAdapter --- .../keychain/ui/adapter/KeyChoiceAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 3e6650f26..10325d083 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -226,7 +226,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { } void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive, boolean isEnabled) { - vName.setText(keyInfo.name()); + vName.setText(keyInfo.user_id()); KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); keyInfoFormatter.formatCreationDate(vCreation); From 794b1dc8e043fdb92df6f2629c419ef680d33250 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 22:28:23 +0200 Subject: [PATCH 109/124] group non-letters in "#" group, display keys with only an address in-order --- .../keychain/ui/adapter/FlexibleKeyItemFactory.java | 11 ++++++++++- .../org/sufficientlysecure/keychain/Keys.sq | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java index 3008dd4ac..139b7cb20 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyItemFactory.java @@ -58,6 +58,15 @@ public class FlexibleKeyItemFactory { @NonNull private String getHeaderText(UnifiedKeyInfo unifiedKeyInfo) { String headerText = unifiedKeyInfo.name(); - return headerText == null || headerText.isEmpty() ? "" : headerText.substring(0, 1).toUpperCase(); + if (headerText == null || headerText.isEmpty()) { + headerText = unifiedKeyInfo.email(); + } + if (headerText == null || headerText.isEmpty()) { + return ""; + } + if (!Character.isLetter(headerText.codePointAt(0))) { + return "#"; + } + return headerText.substring(0, 1).toUpperCase(); } } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index be5b21b48..afa21ed90 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -42,7 +42,7 @@ CREATE VIEW unifiedKeyView AS selectAllUnifiedKeyInfo: SELECT * FROM unifiedKeyView - ORDER BY has_any_secret_int DESC, name COLLATE NOCASE ASC, creation DESC; + ORDER BY has_any_secret_int DESC, IFNULL(name, email) COLLATE NOCASE ASC, creation DESC; selectUnifiedKeyInfoByMasterKeyId: SELECT * FROM unifiedKeyView From 126224e7510685937ff91e6870b1572c887c5045 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 3 Jul 2018 22:31:31 +0200 Subject: [PATCH 110/124] ditch unused KeyUpdateHelper class --- .../keychain/util/KeyUpdateHelper.java | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java deleted file mode 100644 index cb294b6c1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.util; - -public class KeyUpdateHelper { - - /* - - public void updateAllKeys(Context context, KeychainIntentServiceHandler finishedHandler) { - UpdateTask updateTask = new UpdateTask(context, finishedHandler); - updateTask.execute(); - } - - private class UpdateTask extends AsyncTask { - private Context mContext; - private KeychainIntentServiceHandler mHandler; - - public UpdateTask(Context context, KeychainIntentServiceHandler handler) { - this.mContext = context; - this.mHandler = handler; - } - - @Override - protected Void doInBackground(Void... voids) { - ProviderHelper providerHelper = new ProviderHelper(mContext); - List keys = new ArrayList(); - String[] servers = Preferences.getPreferences(mContext).getKeyServers(); - - if (servers != null && servers.length > 0) { - // Load all the fingerprints in the database and prepare to import them - for (String fprint : providerHelper.getAllFingerprints(KeychainContract.KeyRings.buildUnifiedKeyRingsUri())) { - ImportKeysListEntry key = new ImportKeysListEntry(); - key.setFingerprintHex(fprint); - key.addOrigin(servers[0]); - keys.add(key); - } - - // Start the service and update the keys - Intent importIntent = new Intent(mContext, KeychainService.class); - importIntent.setAction(KeychainService.ACTION_DOWNLOAD_AND_IMPORT_KEYS); - - Bundle importData = new Bundle(); - importData.putParcelableArrayList(KeychainService.DOWNLOAD_KEY_LIST, - new ArrayList(keys)); - importIntent.putExtra(KeychainService.EXTRA_SERVICE_INTENT, importData); - - importIntent.putExtra(KeychainService.EXTRA_MESSENGER, new Messenger(mHandler)); - - mContext.startService(importIntent); - } - return null; - } - } - */ - -} From b95c885aa0da86a98b247be93580750251f26e03 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 00:29:41 +0200 Subject: [PATCH 111/124] fix race condition crash in ViewKeyActivity --- .../keychain/ui/keyview/ViewKeyActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 3385c2283..156b5c3a4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -352,6 +352,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements @Override public boolean onPrepareOptionsMenu(Menu menu) { + if (unifiedKeyInfo == null) { + return false; + } MenuItem backupKey = menu.findItem(R.id.menu_key_view_backup); backupKey.setVisible(unifiedKeyInfo.has_any_secret()); menu.findItem(R.id.menu_key_view_skt).setVisible(unifiedKeyInfo.has_any_secret()); From 8a33fa854066a6502a209fd9aee73a89fc9b00a5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 12:51:23 +0200 Subject: [PATCH 112/124] make search in uidList case insensitive in all places --- .../sufficientlysecure/keychain/model/SubKey.java | 12 ++++++++++++ .../ui/dialog/RemoteSelectIdentityKeyPresenter.java | 11 +++++------ .../keychain/ui/adapter/FlexibleKeyDetailsItem.java | 3 +-- .../ui/chips/EncryptRecipientChipsInput.java | 3 +-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 06508b7ec..e035917eb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -29,6 +29,7 @@ public abstract class SubKey implements KeysModel { @AutoValue public static abstract class UnifiedKeyInfo implements KeysModel.UnifiedKeyViewModel { private List autocryptPackageNames; + private String cachedUidSearchString; public boolean is_expired() { Long expiry = expiry(); @@ -64,5 +65,16 @@ public abstract class SubKey implements KeysModel { public boolean has_encrypt_key() { return has_encrypt_key_int() != 0; } + + public String uidSearchString() { + if (cachedUidSearchString == null) { + cachedUidSearchString = user_id_list(); + if (cachedUidSearchString == null) { + cachedUidSearchString = ""; + } + cachedUidSearchString = cachedUidSearchString.toLowerCase(); + } + return cachedUidSearchString; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java index abc4695b9..19ab30368 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdentityKeyPresenter.java @@ -112,8 +112,8 @@ class RemoteSelectIdentityKeyPresenter { } private void goToSelectLayout() { - List filteredKeyInfoData = - viewModel.isListAllKeys() || TextUtils.isEmpty(userId.email) ? keyInfoData : getFilteredKeyInfo(); + List filteredKeyInfoData = viewModel.isListAllKeys() || TextUtils.isEmpty(userId.email) ? + keyInfoData : getFilteredKeyInfo(userId.email.toLowerCase().trim()); if (filteredKeyInfoData == null) { view.showLayoutEmpty(); @@ -125,12 +125,11 @@ class RemoteSelectIdentityKeyPresenter { } } - private List getFilteredKeyInfo() { + private List getFilteredKeyInfo(String filterString) { if (viewModel.filteredKeyInfo == null) { viewModel.filteredKeyInfo = new ArrayList<>(); for (UnifiedKeyInfo unifiedKeyInfo : keyInfoData) { - String emailSearchList = unifiedKeyInfo.user_id_list(); - if (emailSearchList == null || emailSearchList.contains(userId.email)) { + if (unifiedKeyInfo.uidSearchString().contains(filterString)) { viewModel.filteredKeyInfo.add(unifiedKeyInfo); } } @@ -185,7 +184,7 @@ class RemoteSelectIdentityKeyPresenter { } void onKeyItemClick(int position) { - selectedMasterKeyId = getFilteredKeyInfo().get(position).master_key_id(); + selectedMasterKeyId = getFilteredKeyInfo(userId.email.toLowerCase()).get(position).master_key_id(); view.highlightKey(position); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java index 16c7a9050..932a139af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/FlexibleKeyDetailsItem.java @@ -66,8 +66,7 @@ public class FlexibleKeyDetailsItem extends FlexibleSectionableKeyItem @Override public boolean isKeptForConstraint(CharSequence constraint) { - String uidList = keyInfo.user_id_list(); - return uidList == null || uidList.contains(constraint); + return keyInfo.uidSearchString().contains(constraint); } } From 035a62e9200eacf5ece2c2551c6ecdd0abe63458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 4 Jul 2018 13:32:35 +0200 Subject: [PATCH 113/124] check for key status in legacy API key lists --- .../sufficientlysecure/keychain/model/SubKey.java | 4 ++++ .../remote/ui/SelectPublicKeyFragment.java | 12 +++++++++++- .../remote/ui/SelectSignKeyIdListFragment.java | 14 +++++++++++++- .../keychain/ui/adapter/KeyChoiceAdapter.java | 6 ++++++ OpenKeychain/src/main/res/values/strings.xml | 5 +++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index e035917eb..5937ab318 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -66,6 +66,10 @@ public abstract class SubKey implements KeysModel { return has_encrypt_key_int() != 0; } + public boolean has_sign_key() { + return has_sign_key_int() != 0; + } + public String uidSearchString() { if (cachedUidSearchString == null) { cachedUidSearchString = user_id_list(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index 1a01edfda..8603a1b62 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -124,7 +124,17 @@ public class SelectPublicKeyFragment extends RecyclerFragment public void onLoadUnifiedKeyData(List data) { if (keyChoiceAdapter == null) { keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data, (keyInfo -> { - return keyInfo.is_revoked() ? R.string.keychoice_cannot_encrypt : null; + if (keyInfo.is_revoked()) { + return R.string.keychoice_revoked; + } else if (keyInfo.is_expired()) { + return R.string.keychoice_expired; + } else if (!keyInfo.is_secure()) { + return R.string.keychoice_insecure; + } else if (!keyInfo.has_encrypt_key()) { + return R.string.keychoice_cannot_encrypt; + } else { + return null; + } })); setAdapter(keyChoiceAdapter); keyChoiceAdapter.setSelectionByIds(selectedMasterKeyIds); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 3cefc75ba..fd9165fd4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -116,7 +116,19 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createSingleClickableAdapter(data, this::onSelectKeyItemClicked); + keyChoiceAdapter = KeyChoiceAdapter.createSingleClickableAdapter(data, this::onSelectKeyItemClicked, (keyInfo -> { + if (keyInfo.is_revoked()) { + return R.string.keychoice_revoked; + } else if (keyInfo.is_expired()) { + return R.string.keychoice_expired; + } else if (!keyInfo.is_secure()) { + return R.string.keychoice_insecure; + } else if (!keyInfo.has_sign_key()) { + return R.string.keychoice_cannot_sign; + } else { + return null; + } + })); setAdapter(keyChoiceAdapter); } else { keyChoiceAdapter.setUnifiedKeyInfoItems(data); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 10325d083..3d1dd655a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -40,6 +40,12 @@ public class KeyChoiceAdapter extends FlexibleAdapter { return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE, null); } + public static KeyChoiceAdapter createSingleClickableAdapter(List items, + OnKeyClickListener onKeyClickListener, + KeyDisabledPredicate keyDisabledPredicate) { + return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE, keyDisabledPredicate); + } + public static KeyChoiceAdapter createSingleChoiceAdapter(List items) { return new KeyChoiceAdapter(items, null, Mode.SINGLE, null); } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 5ad862968..931926c8c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -2035,4 +2035,9 @@ An error occurred while updating all keys This key cannot be used for encryption! + This key cannot be used for signing! + This key cannot be used because it is insecure! + This key cannot be used because it is revoked! + This key cannot be used because it is expired! + From 24090cbf18faac2e01d7b1aa3671d9dfc0476a7d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 13:21:50 +0200 Subject: [PATCH 114/124] fix "Create new key" layout in SelectSignKeyIdListFragment --- .../keychain/remote/ui/SelectSignKeyIdListFragment.java | 2 +- OpenKeychain/src/main/res/layout/select_dummy_key_item.xml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index fd9165fd4..c7a52d2f8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -83,7 +83,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment \ No newline at end of file + android:minHeight="?listPreferredItemHeight" + /> \ No newline at end of file From d0a9cea74e6375b0a38de43c0acaf3fe7d80a3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 4 Jul 2018 13:42:43 +0200 Subject: [PATCH 115/124] disabled keys for RemoteDeduplicatePresenter --- .../ui/dialog/RemoteDeduplicatePresenter.java | 15 ++++++++++++++- .../keychain/ui/adapter/KeyChoiceAdapter.java | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index 43ec37551..5b9de599c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -24,6 +24,7 @@ import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.support.v7.widget.RecyclerView.Adapter; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.remote.AutocryptInteractor; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteDeduplicateActivity.DeduplicateViewModel; @@ -62,7 +63,19 @@ class RemoteDeduplicatePresenter { private void onLoadKeyInfos(List data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createSingleChoiceAdapter(data); + keyChoiceAdapter = KeyChoiceAdapter.createSingleChoiceAdapter(data, (keyInfo -> { + if (keyInfo.is_revoked()) { + return R.string.keychoice_revoked; + } else if (keyInfo.is_expired()) { + return R.string.keychoice_expired; + } else if (!keyInfo.is_secure()) { + return R.string.keychoice_insecure; + } else if (!keyInfo.has_encrypt_key()) { + return R.string.keychoice_cannot_encrypt; + } else { + return null; + } + })); view.setKeyListAdapter(keyChoiceAdapter); } else { keyChoiceAdapter.setUnifiedKeyInfoItems(data); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java index 3d1dd655a..b2f49278b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyChoiceAdapter.java @@ -50,6 +50,10 @@ public class KeyChoiceAdapter extends FlexibleAdapter { return new KeyChoiceAdapter(items, null, Mode.SINGLE, null); } + public static KeyChoiceAdapter createSingleChoiceAdapter(List items, KeyDisabledPredicate keyDisabledPredicate) { + return new KeyChoiceAdapter(items, null, Mode.SINGLE, keyDisabledPredicate); + } + public static KeyChoiceAdapter createMultiChoiceAdapter(List items, KeyDisabledPredicate keyDisabledPredicate) { return new KeyChoiceAdapter(items, null, Mode.MULTI, keyDisabledPredicate); } From 5c13b21577826da70793492c0eff094d5be0d590 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 13:33:09 +0200 Subject: [PATCH 116/124] Support stable ids in chip adapters --- .../keychain/ui/chips/EncryptRecipientChipsInput.java | 5 +++++ .../com/pchmn/materialchips/adapter/ChipsAdapter.java | 8 +++++++- .../pchmn/materialchips/adapter/FilterableAdapter.java | 9 +++++++++ .../com/pchmn/materialchips/model/ChipInterface.java | 1 - .../java/com/pchmn/materialchips/simple/SimpleChip.java | 8 ++++---- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java index 91887b1ef..5bdfc4f03 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -40,6 +40,11 @@ public class EncryptRecipientChipsInput extends ChipsInput this.keyInfo = keyInfo; } + @Override + public long getId() { + return keyInfo.master_key_id(); + } + @Override public boolean isKeptForConstraint(CharSequence constraint) { return keyInfo.uidSearchString().contains(constraint); diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java index df012a5d2..8a0ef8a25 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -45,6 +45,7 @@ public abstract class ChipsAdapter itemList) { itemFilter = new ItemFilter(itemList); displayedList.addAll(itemList); + + setHasStableIds(true); } @Override @@ -35,6 +37,12 @@ public abstract class FilterableAdapter originalList; private List filteredList; @@ -86,6 +94,7 @@ public abstract class FilterableAdapter Date: Wed, 4 Jul 2018 16:35:11 +0200 Subject: [PATCH 117/124] fix preselecting multiple encryption recipients --- .../ui/EncryptModeAsymmetricFragment.java | 58 +++++-------------- .../ui/chips/EncryptRecipientChipsInput.java | 20 +++++++ .../com/pchmn/materialchips/ChipsInput.java | 17 +----- .../materialchips/adapter/ChipsAdapter.java | 34 ++++------- 4 files changed, 50 insertions(+), 79 deletions(-) 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 4264575c9..aa5237dc2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -51,15 +51,14 @@ import timber.log.Timber; public class EncryptModeAsymmetricFragment extends EncryptModeFragment { + public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; + public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; + KeyRepository keyRepository; private KeySpinner mSignKeySpinner; private EncryptRecipientChipsInput mEncryptKeyView; - public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; - public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; - - public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) { EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment(); @@ -131,12 +130,19 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { // preselect keys given, from state or arguments if (savedInstanceState == null) { - Long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID); - if (signatureKeyId == Constants.key.none) { - signatureKeyId = null; - } - long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); - preselectKeys(signatureKeyId, encryptionKeyIds); + Bundle arguments = getArguments(); + preselectKeysFromArguments(arguments); + } + } + + private void preselectKeysFromArguments(Bundle arguments) { + long preselectedSignatureKeyId = arguments.getLong(ARG_SINGATURE_KEY_ID); + if (preselectedSignatureKeyId != Constants.key.none) { + mSignKeySpinner.setPreSelectedKeyId(preselectedSignatureKeyId); + } + long[] preselectedEncryptionKeyIds = arguments.getLongArray(ARG_ENCRYPTION_KEY_IDS); + if (preselectedEncryptionKeyIds != null) { + mEncryptKeyView.setPreSelectedKeyIds(preselectedEncryptionKeyIds); } } @@ -171,38 +177,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment { } } - /** - * If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those! - */ - private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) { - if (signatureKeyId != null) { - UnifiedKeyInfo unifiedKeyInfo = keyRepository.getUnifiedKeyInfo(signatureKeyId); - if (unifiedKeyInfo == null) { - String beautifyKeyId = KeyFormattingUtils.beautifyKeyId(signatureKeyId); - Notify.create(getActivity(), getString(R.string.error_preselect_sign_key, beautifyKeyId), Style.ERROR).show(); - } else if (unifiedKeyInfo.has_any_secret()) { - mSignKeySpinner.setPreSelectedKeyId(signatureKeyId); - } - } - - if (encryptionKeyIds != null) { - for (long preselectedId : encryptionKeyIds) { - UnifiedKeyInfo keyInfo = keyRepository.getUnifiedKeyInfo(preselectedId); - if (keyInfo == null) { - Timber.e("key not found for encryption!"); - Notify.create(getActivity(), getString(R.string.error_preselect_encrypt_key, - KeyFormattingUtils.beautifyKeyId(preselectedId)), - Style.ERROR).show(); - } else { - mEncryptKeyView.addChip(EncryptRecipientChipsInput.chipFromUnifiedKeyInfo(keyInfo)); - } - } - - // This is to work-around a rendering bug in TokenCompleteTextView - mEncryptKeyView.requestFocus(); - } - } - @Override public boolean isAsymmetric() { return true; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java index 5bdfc4f03..9ee636e2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -1,6 +1,8 @@ package org.sufficientlysecure.keychain.ui.chips; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import android.content.Context; @@ -13,6 +15,8 @@ import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.Encry public class EncryptRecipientChipsInput extends ChipsInput { + private long[] preselectedKeyIds; + public EncryptRecipientChipsInput(Context context) { super(context); init(); @@ -31,6 +35,22 @@ public class EncryptRecipientChipsInput extends ChipsInput public void setData(List keyInfoChips) { EncryptRecipientDropdownAdapter chipDropdownAdapter = new EncryptRecipientDropdownAdapter(getContext(), keyInfoChips); setChipDropdownAdapter(chipDropdownAdapter); + + if (preselectedKeyIds != null) { + Arrays.sort(preselectedKeyIds); + ArrayList preselectedChips = new ArrayList<>(); + for (EncryptRecipientChip keyInfoChip : keyInfoChips) { + if (Arrays.binarySearch(preselectedKeyIds, keyInfoChip.keyInfo.master_key_id()) >= 0) { + preselectedChips.add(keyInfoChip); + } + } + addChips(preselectedChips); + preselectedKeyIds = null; + } + } + + public void setPreSelectedKeyIds(long[] preselectedEncryptionKeyIds) { + this.preselectedKeyIds = preselectedEncryptionKeyIds; } public static class EncryptRecipientChip implements FilterableItem { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java index 961a1fda4..0abff6721 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -45,7 +45,6 @@ public abstract class ChipsInput extends ScrollViewMax private boolean mShowChipDetailed = true; private List> mChipsListenerList = new ArrayList<>(); - private ChipValidator mChipValidator; private ChipsAdapter chipsAdapter; private RecyclerView chipsRecyclerView; @@ -187,8 +186,8 @@ public abstract class ChipsInput extends ScrollViewMax }); } - public void addChip(T chip) { - chipsAdapter.addChip(chip); + public void addChips(List chips) { + chipsAdapter.addChipsProgrammatically(chips); } public ChipsInputEditText getEditText() { @@ -317,14 +316,6 @@ public abstract class ChipsInput extends ScrollViewMax })); } - public ChipValidator getChipValidator() { - return mChipValidator; - } - - public void setChipValidator(ChipValidator mChipValidator) { - this.mChipValidator = mChipValidator; - } - public interface ChipsListener { void onChipAdded(T chip, int newSize); void onChipRemoved(T chip, int newSize); @@ -338,8 +329,4 @@ public abstract class ChipsInput extends ScrollViewMax public void onTextChanged(CharSequence text) { } public void onActionDone(CharSequence text) { } } - - public interface ChipValidator { - boolean areEquals(T chip1, T chip2); - } } diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java index 8a0ef8a25..38b717403 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -202,17 +202,19 @@ public abstract class ChipsAdapter contactList, T chip) { - if (chipsInput.getChipValidator() != null) { - for (T item : contactList) { - if (chipsInput.getChipValidator().areEquals(item, chip)) { - return true; - } - } - } - - return false; - } } From a218a108808b793a5a7a8b1d9ef852e354f2a320 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 16:39:06 +0200 Subject: [PATCH 118/124] update dependencies in MaterialChipsInput --- extern/MaterialChipsInput/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/MaterialChipsInput/build.gradle b/extern/MaterialChipsInput/build.gradle index d6a67320a..759457a57 100644 --- a/extern/MaterialChipsInput/build.gradle +++ b/extern/MaterialChipsInput/build.gradle @@ -26,11 +26,11 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:27.1.0' + compile 'com.android.support:appcompat-v7:27.1.1' testCompile 'junit:junit:4.12' // recycler - compile 'com.android.support:recyclerview-v7:27.1.0' + compile 'com.android.support:recyclerview-v7:27.1.1' compile 'com.beloo.widget:ChipsLayoutManager:0.3.7@aar' } From a975586086d08ba4db11a3b1ab22a6a5cd46511f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 16:46:34 +0200 Subject: [PATCH 119/124] move MaterialChipsInput to org.sufficientlysecure namespace --- .../ui/EncryptModeAsymmetricFragment.java | 2 +- .../ui/chips/EncryptRecipientChipAdapter.java | 12 +++++----- .../ui/chips/EncryptRecipientChipsInput.java | 4 ++-- .../EncryptRecipientDropdownAdapter.java | 2 +- .../ExampleInstrumentedTest.java | 4 ++-- .../src/main/AndroidManifest.xml | 2 +- .../materialchips/model/ChipInterface.java | 10 --------- .../materialchips/ChipView.java | 8 +++---- .../materialchips/ChipsInput.java | 22 +++++++++---------- .../RecyclerItemClickListener.java | 2 +- .../materialchips/adapter/ChipsAdapter.java | 14 ++++++------ .../adapter/FilterableAdapter.java | 4 ++-- .../materialchips/model/ChipInterface.java | 10 +++++++++ .../materialchips/simple/SimpleChip.java | 4 ++-- .../simple/SimpleChipDropdownAdapter.java | 12 +++++----- .../simple/SimpleChipsAdapter.java | 14 ++++++------ .../simple/SimpleChipsInput.java | 4 ++-- .../materialchips/util/ActivityUtil.java | 2 +- .../util/ClickOutsideCallback.java | 6 ++--- .../materialchips/util/ColorUtil.java | 4 ++-- .../util/DelegateWindowCallback.java | 2 +- .../util/LetterTileProvider.java | 4 ++-- .../materialchips/util/ViewUtil.java | 2 +- .../views/ChipsInputEditText.java | 2 +- .../materialchips/views/DetailedChipView.java | 17 ++++++-------- .../materialchips/views/DropdownListView.java | 8 +++---- .../views/ScrollViewMaxHeight.java | 6 ++--- .../src/main/res/layout/chips_input.xml | 4 ++-- .../src/main/res/values/colors.xml | 1 - .../materialchips/ExampleUnitTest.java | 2 +- 30 files changed, 93 insertions(+), 97 deletions(-) rename extern/MaterialChipsInput/src/androidTest/java/{com/pchmn => org/sufficientlysecure}/materialchips/ExampleInstrumentedTest.java (82%) delete mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/ChipView.java (97%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/ChipsInput.java (93%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/RecyclerItemClickListener.java (97%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/adapter/ChipsAdapter.java (95%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/adapter/FilterableAdapter.java (95%) create mode 100644 extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/model/ChipInterface.java rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/simple/SimpleChip.java (90%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/simple/SimpleChipDropdownAdapter.java (76%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/simple/SimpleChipsAdapter.java (77%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/simple/SimpleChipsInput.java (87%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/util/ActivityUtil.java (90%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/util/ClickOutsideCallback.java (90%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/util/ColorUtil.java (92%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/util/DelegateWindowCallback.java (98%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/util/LetterTileProvider.java (98%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/util/ViewUtil.java (98%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/views/ChipsInputEditText.java (93%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/views/DetailedChipView.java (94%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/views/DropdownListView.java (93%) rename extern/MaterialChipsInput/src/main/java/{com/pchmn => org/sufficientlysecure}/materialchips/views/ScrollViewMaxHeight.java (89%) rename extern/MaterialChipsInput/src/test/java/{com/pchmn => org/sufficientlysecure}/materialchips/ExampleUnitTest.java (88%) 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 aa5237dc2..99caf6076 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -34,7 +34,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ViewAnimator; -import com.pchmn.materialchips.ChipsInput.SimpleChipsListener; +import org.sufficientlysecure.materialchips.ChipsInput.SimpleChipsListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.daos.KeyRepository; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java index 1ed4fb178..929200dad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipAdapter.java @@ -7,12 +7,12 @@ import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; -import com.pchmn.materialchips.ChipView; -import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.adapter.ChipsAdapter; -import com.pchmn.materialchips.simple.SimpleChip; -import com.pchmn.materialchips.util.ViewUtil; -import com.pchmn.materialchips.views.DetailedChipView; +import org.sufficientlysecure.materialchips.ChipView; +import org.sufficientlysecure.materialchips.ChipsInput; +import org.sufficientlysecure.materialchips.adapter.ChipsAdapter; +import org.sufficientlysecure.materialchips.simple.SimpleChip; +import org.sufficientlysecure.materialchips.util.ViewUtil; +import org.sufficientlysecure.materialchips.views.DetailedChipView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java index 9ee636e2a..475366a33 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientChipsInput.java @@ -8,8 +8,8 @@ import java.util.List; import android.content.Context; import android.util.AttributeSet; -import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; +import org.sufficientlysecure.materialchips.ChipsInput; +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter.FilterableItem; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java index 9d8609dca..069019c80 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java @@ -12,7 +12,7 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.pchmn.materialchips.ChipsInput; +import org.sufficientlysecure.materialchips.ChipsInput; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientChipsInput.EncryptRecipientChip; import org.sufficientlysecure.keychain.ui.chips.EncryptRecipientDropdownAdapter.ItemViewHolder; diff --git a/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java b/extern/MaterialChipsInput/src/androidTest/java/org/sufficientlysecure/materialchips/ExampleInstrumentedTest.java similarity index 82% rename from extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java rename to extern/MaterialChipsInput/src/androidTest/java/org/sufficientlysecure/materialchips/ExampleInstrumentedTest.java index a28c3f326..005243292 100644 --- a/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java +++ b/extern/MaterialChipsInput/src/androidTest/java/org/sufficientlysecure/materialchips/ExampleInstrumentedTest.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips; +package org.sufficientlysecure.materialchips; import android.content.Context; import android.support.test.InstrumentationRegistry; @@ -21,6 +21,6 @@ public class ExampleInstrumentedTest { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); - assertEquals("com.pchmn.library.test", appContext.getPackageName()); + assertEquals("org.sufficientlysecure.library.test", appContext.getPackageName()); } } diff --git a/extern/MaterialChipsInput/src/main/AndroidManifest.xml b/extern/MaterialChipsInput/src/main/AndroidManifest.xml index ce7e64f72..1bcab1dfc 100644 --- a/extern/MaterialChipsInput/src/main/AndroidManifest.xml +++ b/extern/MaterialChipsInput/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ + package="org.sufficientlysecure.materialchips"> diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java deleted file mode 100644 index 928e07085..000000000 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.pchmn.materialchips.model; - - -import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; - - -public interface ChipInterface extends FilterableItem { - String getLabel(); - String getInfo(); -} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/ChipView.java similarity index 97% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/ChipView.java index 7b57353e2..f20c523c9 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/ChipView.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips; +package org.sufficientlysecure.materialchips; import android.content.Context; @@ -15,9 +15,9 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.util.LetterTileProvider; -import com.pchmn.materialchips.util.ViewUtil; +import org.sufficientlysecure.materialchips.model.ChipInterface; +import org.sufficientlysecure.materialchips.util.LetterTileProvider; +import org.sufficientlysecure.materialchips.util.ViewUtil; public class ChipView extends RelativeLayout { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/ChipsInput.java similarity index 93% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/ChipsInput.java index 0abff6721..bc0c83e13 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/ChipsInput.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips; +package org.sufficientlysecure.materialchips; import java.util.ArrayList; @@ -23,16 +23,16 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager; -import com.pchmn.materialchips.RecyclerItemClickListener.OnItemClickListener; -import com.pchmn.materialchips.adapter.ChipsAdapter; -import com.pchmn.materialchips.adapter.FilterableAdapter; -import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; -import com.pchmn.materialchips.util.ActivityUtil; -import com.pchmn.materialchips.util.ClickOutsideCallback; -import com.pchmn.materialchips.util.ViewUtil; -import com.pchmn.materialchips.views.ChipsInputEditText; -import com.pchmn.materialchips.views.DropdownListView; -import com.pchmn.materialchips.views.ScrollViewMaxHeight; +import org.sufficientlysecure.materialchips.RecyclerItemClickListener.OnItemClickListener; +import org.sufficientlysecure.materialchips.adapter.ChipsAdapter; +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter; +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter.FilterableItem; +import org.sufficientlysecure.materialchips.util.ActivityUtil; +import org.sufficientlysecure.materialchips.util.ClickOutsideCallback; +import org.sufficientlysecure.materialchips.util.ViewUtil; +import org.sufficientlysecure.materialchips.views.ChipsInputEditText; +import org.sufficientlysecure.materialchips.views.DropdownListView; +import org.sufficientlysecure.materialchips.views.ScrollViewMaxHeight; public abstract class ChipsInput extends ScrollViewMaxHeight { private Context mContext; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/RecyclerItemClickListener.java similarity index 97% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/RecyclerItemClickListener.java index 2854f9ebf..0e5f13b0a 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/RecyclerItemClickListener.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/RecyclerItemClickListener.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips; +package org.sufficientlysecure.materialchips; import android.content.Context; import android.support.v7.widget.RecyclerView; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/adapter/ChipsAdapter.java similarity index 95% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/adapter/ChipsAdapter.java index 38b717403..095fdcb85 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/adapter/ChipsAdapter.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.adapter; +package org.sufficientlysecure.materialchips.adapter; import java.util.ArrayList; @@ -15,12 +15,12 @@ import android.view.ViewTreeObserver; import android.widget.EditText; import android.widget.RelativeLayout; -import com.pchmn.materialchips.ChipView; -import com.pchmn.materialchips.ChipsInput; -import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; -import com.pchmn.materialchips.util.ViewUtil; -import com.pchmn.materialchips.views.ChipsInputEditText; -import com.pchmn.materialchips.views.DetailedChipView; +import org.sufficientlysecure.materialchips.ChipView; +import org.sufficientlysecure.materialchips.ChipsInput; +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter.FilterableItem; +import org.sufficientlysecure.materialchips.util.ViewUtil; +import org.sufficientlysecure.materialchips.views.ChipsInputEditText; +import org.sufficientlysecure.materialchips.views.DetailedChipView; public abstract class ChipsAdapter diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/adapter/FilterableAdapter.java similarity index 95% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/adapter/FilterableAdapter.java index 0d420e35e..2731768ad 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/adapter/FilterableAdapter.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.adapter; +package org.sufficientlysecure.materialchips.adapter; import java.util.ArrayList; @@ -8,7 +8,7 @@ import android.support.v7.widget.RecyclerView; import android.widget.Filter; import android.widget.Filterable; -import com.pchmn.materialchips.adapter.FilterableAdapter.FilterableItem; +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter.FilterableItem; public abstract class FilterableAdapter extends RecyclerView.Adapter implements Filterable { diff --git a/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/model/ChipInterface.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/model/ChipInterface.java new file mode 100644 index 000000000..7c0ac7d91 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/model/ChipInterface.java @@ -0,0 +1,10 @@ +package org.sufficientlysecure.materialchips.model; + + +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter.FilterableItem; + + +public interface ChipInterface extends FilterableItem { + String getLabel(); + String getInfo(); +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChip.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChip.java similarity index 90% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChip.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChip.java index 608460d52..eb4d3e5c9 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChip.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChip.java @@ -1,10 +1,10 @@ -package com.pchmn.materialchips.simple; +package org.sufficientlysecure.materialchips.simple; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.pchmn.materialchips.model.ChipInterface; +import org.sufficientlysecure.materialchips.model.ChipInterface; public class SimpleChip implements ChipInterface { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipDropdownAdapter.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChipDropdownAdapter.java similarity index 76% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipDropdownAdapter.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChipDropdownAdapter.java index 7601d4fe2..4595a1b97 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipDropdownAdapter.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChipDropdownAdapter.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.simple; +package org.sufficientlysecure.materialchips.simple; import java.util.List; @@ -11,9 +11,9 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.pchmn.materialchips.ChipsInput.ChipDropdownAdapter; -import com.pchmn.materialchips.R; -import com.pchmn.materialchips.simple.SimpleChipDropdownAdapter.ItemViewHolder; +import org.sufficientlysecure.materialchips.ChipsInput.ChipDropdownAdapter; +import org.sufficientlysecure.materialchips.R; +import org.sufficientlysecure.materialchips.simple.SimpleChipDropdownAdapter.ItemViewHolder; public class SimpleChipDropdownAdapter extends ChipDropdownAdapter { @@ -31,8 +31,8 @@ public class SimpleChipDropdownAdapter extends ChipDropdownAdapter { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChipsInput.java similarity index 87% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChipsInput.java index 735636ee5..46f997b2b 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/simple/SimpleChipsInput.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/simple/SimpleChipsInput.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.simple; +package org.sufficientlysecure.materialchips.simple; import java.util.List; @@ -6,7 +6,7 @@ import java.util.List; import android.content.Context; import android.util.AttributeSet; -import com.pchmn.materialchips.ChipsInput; +import org.sufficientlysecure.materialchips.ChipsInput; public class SimpleChipsInput extends ChipsInput { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ActivityUtil.java similarity index 90% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ActivityUtil.java index c119fad07..6eacc3243 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ActivityUtil.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.util; +package org.sufficientlysecure.materialchips.util; import android.app.Activity; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ClickOutsideCallback.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ClickOutsideCallback.java similarity index 90% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ClickOutsideCallback.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ClickOutsideCallback.java index 367e57d57..e66f60433 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ClickOutsideCallback.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ClickOutsideCallback.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.util; +package org.sufficientlysecure.materialchips.util; import android.app.Activity; import android.content.Context; @@ -8,8 +8,8 @@ import android.view.View; import android.view.Window; import android.view.inputmethod.InputMethodManager; -import com.pchmn.materialchips.views.ChipsInputEditText; -import com.pchmn.materialchips.views.DetailedChipView; +import org.sufficientlysecure.materialchips.views.ChipsInputEditText; +import org.sufficientlysecure.materialchips.views.DetailedChipView; public class ClickOutsideCallback extends DelegateWindowCallback { private Activity activity; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ColorUtil.java similarity index 92% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ColorUtil.java index c1b118c1c..4ced65b84 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ColorUtil.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.util; +package org.sufficientlysecure.materialchips.util; import android.content.Context; @@ -6,7 +6,7 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.util.TypedValue; -import com.pchmn.materialchips.R; +import org.sufficientlysecure.materialchips.R; public class ColorUtil { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/DelegateWindowCallback.java similarity index 98% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/DelegateWindowCallback.java index 3dad594c3..1f553143c 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/DelegateWindowCallback.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/DelegateWindowCallback.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.util; +package org.sufficientlysecure.materialchips.util; import android.os.Build; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/LetterTileProvider.java similarity index 98% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/LetterTileProvider.java index 9028a5286..48e64c848 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/LetterTileProvider.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.util; +package org.sufficientlysecure.materialchips.util; import android.content.Context; import android.content.res.Resources; @@ -18,7 +18,7 @@ import android.support.v4.content.ContextCompat; import android.text.TextPaint; import android.util.Log; -import com.pchmn.materialchips.R; +import org.sufficientlysecure.materialchips.R; /** * Used to create a {@link Bitmap} that contains a letter used in the English diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ViewUtil.java similarity index 98% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ViewUtil.java index 1cae87da7..dbde7fdf7 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/util/ViewUtil.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.util; +package org.sufficientlysecure.materialchips.util; import android.content.Context; import android.content.res.Configuration; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/ChipsInputEditText.java similarity index 93% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/ChipsInputEditText.java index 9c2b108c5..07b910490 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/ChipsInputEditText.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.views; +package org.sufficientlysecure.materialchips.views; import android.content.Context; diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/DetailedChipView.java similarity index 94% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/DetailedChipView.java index b5a7f6eb3..cd140e11a 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/DetailedChipView.java @@ -1,25 +1,22 @@ -package com.pchmn.materialchips.views; +package org.sufficientlysecure.materialchips.views; + import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.net.Uri; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import android.view.animation.AlphaAnimation; import android.widget.ImageButton; import android.widget.LinearLayout; -import android.widget.LinearLayout; import android.widget.TextView; -import com.pchmn.materialchips.R; -import com.pchmn.materialchips.model.ChipInterface; -import com.pchmn.materialchips.util.ColorUtil; -import com.pchmn.materialchips.util.LetterTileProvider; +import org.sufficientlysecure.materialchips.R; +import org.sufficientlysecure.materialchips.model.ChipInterface; +import org.sufficientlysecure.materialchips.util.ColorUtil; +import org.sufficientlysecure.materialchips.util.LetterTileProvider; public class DetailedChipView extends LinearLayout { @@ -132,7 +129,7 @@ public class DetailedChipView extends LinearLayout { } public int getBackgroundColor() { - return mBackgroundColor == null ? ContextCompat.getColor(mContext, R.color.colorAccent) : mBackgroundColor.getDefaultColor(); + return mBackgroundColor == null ? ContextCompat.getColor(mContext, R.color.chips_opened_bg) : mBackgroundColor.getDefaultColor(); } public void setDeleteIconColor(ColorStateList color) { diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/DropdownListView.java similarity index 93% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/DropdownListView.java index d44019dac..c36c061fd 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DropdownListView.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/DropdownListView.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.views; +package org.sufficientlysecure.materialchips.views; import android.annotation.SuppressLint; @@ -13,9 +13,9 @@ import android.view.ViewTreeObserver; import android.view.animation.AlphaAnimation; import android.widget.RelativeLayout; -import com.pchmn.materialchips.R; -import com.pchmn.materialchips.adapter.FilterableAdapter; -import com.pchmn.materialchips.util.ViewUtil; +import org.sufficientlysecure.materialchips.R; +import org.sufficientlysecure.materialchips.adapter.FilterableAdapter; +import org.sufficientlysecure.materialchips.util.ViewUtil; @SuppressLint("ViewConstructor") // this is a dropdown view, it doesn't come up in preview diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/ScrollViewMaxHeight.java similarity index 89% rename from extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java rename to extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/ScrollViewMaxHeight.java index db212ff65..92fbfb131 100644 --- a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java +++ b/extern/MaterialChipsInput/src/main/java/org/sufficientlysecure/materialchips/views/ScrollViewMaxHeight.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips.views; +package org.sufficientlysecure.materialchips.views; import android.content.Context; @@ -6,8 +6,8 @@ import android.content.res.TypedArray; import android.support.v4.widget.NestedScrollView; import android.util.AttributeSet; -import com.pchmn.materialchips.R; -import com.pchmn.materialchips.util.ViewUtil; +import org.sufficientlysecure.materialchips.R; +import org.sufficientlysecure.materialchips.util.ViewUtil; public class ScrollViewMaxHeight extends NestedScrollView { diff --git a/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml b/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml index f39d6fc70..87f8786c2 100644 --- a/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml +++ b/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/colors.xml b/extern/MaterialChipsInput/src/main/res/values/colors.xml index 9f540beb8..da3e72dcb 100644 --- a/extern/MaterialChipsInput/src/main/res/values/colors.xml +++ b/extern/MaterialChipsInput/src/main/res/values/colors.xml @@ -5,6 +5,5 @@ #009688 #ababab #b9ffffff - ?attr/colorAccent \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java b/extern/MaterialChipsInput/src/test/java/org/sufficientlysecure/materialchips/ExampleUnitTest.java similarity index 88% rename from extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java rename to extern/MaterialChipsInput/src/test/java/org/sufficientlysecure/materialchips/ExampleUnitTest.java index b68d97902..f5a54cacb 100644 --- a/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java +++ b/extern/MaterialChipsInput/src/test/java/org/sufficientlysecure/materialchips/ExampleUnitTest.java @@ -1,4 +1,4 @@ -package com.pchmn.materialchips; +package org.sufficientlysecure.materialchips; import org.junit.Test; From 75cf861674c40534421087feb716c1788e71ca3a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 19:39:28 +0200 Subject: [PATCH 120/124] Cache instances of KeyInfoFormatter --- .../remote/ApiPendingIntentFactory.java | 2 +- .../AppSettingsAllowedKeysListFragment.java | 2 +- .../remote/ui/SelectPublicKeyFragment.java | 2 +- .../ui/SelectSignKeyIdListFragment.java | 2 +- .../ui/dialog/DialogKeyChoiceAdapter.java | 109 ++++++++++++++++++ .../ui/dialog/RemoteDeduplicatePresenter.java | 2 +- ...RemoteSelectAuthenticationKeyActivity.java | 108 ++--------------- .../ui/dialog/RemoteSelectIdKeyActivity.java | 92 +-------------- .../keychain/ui/DebugActionsActivity.java | 5 + .../keychain/ui/KeyListFragment.java | 2 + .../ui/adapter/FlexibleKeyDetailsItem.java | 6 +- .../keychain/ui/adapter/KeyChoiceAdapter.java | 68 +++++------ .../ui/bindings/ImportKeysBindingsUtils.java | 3 +- .../EncryptRecipientDropdownAdapter.java | 16 ++- .../keychain/ui/util/Highlighter.java | 7 +- .../keychain/ui/util/KeyInfoFormatter.java | 14 ++- .../ui/widget/KeyChoiceSpinnerAdapter.java | 15 +-- .../api_remote_select_authentication_key.xml | 2 +- .../res/layout/authentication_key_item.xml | 44 ------- .../src/main/res/layout/key_choice_item.xml | 4 +- 20 files changed, 214 insertions(+), 291 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/DialogKeyChoiceAdapter.java delete mode 100644 OpenKeychain/src/main/res/layout/authentication_key_item.xml diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 9dfb8f1ff..085299aaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -156,7 +156,7 @@ public class ApiPendingIntentFactory { return createInternal(data, intent); } - PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) { + public PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) { Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class); intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index 7abead888..e85806428 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -89,7 +89,7 @@ public class AppSettingsAllowedKeysListFragment extends RecyclerFragment data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data, null); + keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(requireContext(), data, null); setAdapter(keyChoiceAdapter); Set checkedIds = apiAppDao.getAllowedKeyIdsForApp(packageName); keyChoiceAdapter.setSelectionByIds(checkedIds); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java index 8603a1b62..f696f78e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -123,7 +123,7 @@ public class SelectPublicKeyFragment extends RecyclerFragment public void onLoadUnifiedKeyData(List data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(data, (keyInfo -> { + keyChoiceAdapter = KeyChoiceAdapter.createMultiChoiceAdapter(requireContext(), data, (keyInfo -> { if (keyInfo.is_revoked()) { return R.string.keychoice_revoked; } else if (keyInfo.is_expired()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index c7a52d2f8..557f606df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -116,7 +116,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createSingleClickableAdapter(data, this::onSelectKeyItemClicked, (keyInfo -> { + keyChoiceAdapter = KeyChoiceAdapter.createSingleClickableAdapter(requireContext(), data, this::onSelectKeyItemClicked, (keyInfo -> { if (keyInfo.is_revoked()) { return R.string.keychoice_revoked; } else if (keyInfo.is_expired()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/DialogKeyChoiceAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/DialogKeyChoiceAdapter.java new file mode 100644 index 000000000..7e3b0864f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/DialogKeyChoiceAdapter.java @@ -0,0 +1,109 @@ +package org.sufficientlysecure.keychain.remote.ui.dialog; + + +import java.util.List; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; +import org.sufficientlysecure.keychain.remote.ui.dialog.DialogKeyChoiceAdapter.KeyChoiceViewHolder; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; + + +class DialogKeyChoiceAdapter extends Adapter { + private final LayoutInflater layoutInflater; + private List data; + private Drawable iconUnselected; + private Drawable iconSelected; + private Integer activeItem; + private KeyInfoFormatter keyInfoFormatter; + + DialogKeyChoiceAdapter(Context context, LayoutInflater layoutInflater) { + this.layoutInflater = layoutInflater; + this.keyInfoFormatter = new KeyInfoFormatter(context); + } + + @NonNull + @Override + public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View keyChoiceItemView = layoutInflater.inflate(R.layout.api_select_identity_item, parent, false); + return new KeyChoiceViewHolder(keyChoiceItemView); + } + + void setActiveItem(Integer activeItem) { + this.activeItem = activeItem; + notifyDataSetChanged(); + } + + @Override + public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { + UnifiedKeyInfo keyInfo = data.get(position); + boolean hasActiveItem = activeItem != null; + boolean isActiveItem = hasActiveItem && position == activeItem; + + Drawable icon = isActiveItem ? iconSelected : iconUnselected; + holder.bind(keyInfo, icon); + + holder.itemView.setVisibility(!hasActiveItem || isActiveItem ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public int getItemCount() { + return data != null ? data.size() : 0; + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + + void setSelectionDrawables(Drawable iconSelected, Drawable iconUnselected) { + this.iconSelected = iconSelected; + this.iconUnselected = iconUnselected; + + notifyDataSetChanged(); + } + + class KeyChoiceViewHolder extends RecyclerView.ViewHolder { + private final TextView vName; + private final TextView vCreation = (TextView) itemView.findViewById(R.id.key_list_item_creation); + private final ImageView vIcon; + + KeyChoiceViewHolder(View itemView) { + super(itemView); + + vName = itemView.findViewById(R.id.key_list_item_name); + vIcon = itemView.findViewById(R.id.key_list_item_icon); + } + + void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { + Context context = vCreation.getContext(); + + keyInfoFormatter.setKeyInfo(keyInfo); + + String email = keyInfo.email(); + String name = keyInfo.name(); + if (email != null) { + vName.setText(context.getString(R.string.use_key, email)); + } else if (name != null) { + vName.setText(context.getString(R.string.use_key, name)); + } else { + vName.setText(context.getString(R.string.use_key_no_name)); + } + + keyInfoFormatter.formatCreationDate(vCreation); + + vIcon.setImageDrawable(selectionIcon); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java index 5b9de599c..6464b4add 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteDeduplicatePresenter.java @@ -63,7 +63,7 @@ class RemoteDeduplicatePresenter { private void onLoadKeyInfos(List data) { if (keyChoiceAdapter == null) { - keyChoiceAdapter = KeyChoiceAdapter.createSingleChoiceAdapter(data, (keyInfo -> { + keyChoiceAdapter = KeyChoiceAdapter.createSingleChoiceAdapter(context, data, (keyInfo -> { if (keyInfo.is_revoked()) { return R.string.keychoice_revoked; } else if (keyInfo.is_expired()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java index 9614297d7..931ddb44a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectAuthenticationKeyActivity.java @@ -40,27 +40,22 @@ import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.Adapter; -import android.text.format.DateUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; -import android.widget.TextView; import com.mikepenz.materialdrawer.util.KeyboardUtil; import org.openintents.ssh.authentication.SshAuthenticationApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.livedata.GenericLiveData; -import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.daos.ApiAppDao; import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; -import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; @@ -203,7 +198,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { @NonNull private RemoteSelectAuthenticationKeyView createMvpView(View view, LayoutInflater layoutInflater) { final ImageView iconClientApp = view.findViewById(R.id.icon_client_app); - final KeyChoiceAdapter keyChoiceAdapter = new KeyChoiceAdapter(layoutInflater, getResources()); + final DialogKeyChoiceAdapter keyChoiceAdapter = new DialogKeyChoiceAdapter(requireContext(), layoutInflater); keyChoiceList.setAdapter(keyChoiceAdapter); return new RemoteSelectAuthenticationKeyView() { @@ -231,7 +226,14 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { @Override public void setTitleClientIcon(Drawable drawable) { iconClientApp.setImageDrawable(drawable); - keyChoiceAdapter.setSelectionDrawable(drawable); + + Resources resources = getResources(); + ConstantState constantState = drawable.getConstantState(); + Drawable iconSelected = constantState.newDrawable(resources); + Drawable iconUnselected = constantState.newDrawable(resources); + DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); + + keyChoiceAdapter.setSelectionDrawables(iconSelected, iconUnselected); } @Override @@ -258,92 +260,4 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity { (view, position) -> presenter.onKeyItemClick(position))); } } - - private static class KeyChoiceAdapter extends Adapter { - private final LayoutInflater layoutInflater; - private final Resources resources; - private List data; - private Drawable iconUnselected; - private Drawable iconSelected; - private Integer activeItem; - - KeyChoiceAdapter(LayoutInflater layoutInflater, Resources resources) { - this.layoutInflater = layoutInflater; - this.resources = resources; - } - - @NonNull - @Override - public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View keyChoiceItemView = layoutInflater.inflate(R.layout.authentication_key_item, parent, false); - return new KeyChoiceViewHolder(keyChoiceItemView); - } - - @Override - public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { - UnifiedKeyInfo keyInfo = data.get(position); - Drawable icon = (activeItem != null && position == activeItem) ? iconSelected : iconUnselected; - holder.bind(keyInfo, icon); - } - - @Override - public int getItemCount() { - return data != null ? data.size() : 0; - } - - public void setData(List data) { - this.data = data; - notifyDataSetChanged(); - } - - void setSelectionDrawable(Drawable drawable) { - ConstantState constantState = drawable.getConstantState(); - if (constantState == null) { - return; - } - - iconSelected = constantState.newDrawable(resources); - - iconUnselected = constantState.newDrawable(resources); - DrawableCompat.setTint(iconUnselected.mutate(), ResourcesCompat.getColor(resources, R.color.md_grey_300, null)); - - notifyDataSetChanged(); - } - - void setActiveItem(Integer newActiveItem) { - Integer prevActiveItem = this.activeItem; - this.activeItem = newActiveItem; - - if (prevActiveItem != null) { - notifyItemChanged(prevActiveItem); - } - if (newActiveItem != null) { - notifyItemChanged(newActiveItem); - } - } - } - - private static class KeyChoiceViewHolder extends RecyclerView.ViewHolder { - private final TextView vName; - private final TextView vCreation; - private final ImageView vIcon; - - KeyChoiceViewHolder(View itemView) { - super(itemView); - - vName = itemView.findViewById(R.id.key_list_item_name); - vCreation = itemView.findViewById(R.id.key_list_item_creation); - vIcon = itemView.findViewById(R.id.key_list_item_icon); - } - - void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { - vName.setText(keyInfo.name()); - - KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); - keyInfoFormatter.formatCreationDate(vCreation); - - vIcon.setImageDrawable(selectionIcon); - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java index ce5eb16c0..3370ebfb6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/dialog/RemoteSelectIdKeyActivity.java @@ -45,8 +45,6 @@ import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.Adapter; -import android.text.format.DateUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -60,18 +58,17 @@ import android.widget.Toast; import com.mikepenz.materialdrawer.util.KeyboardUtil; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.livedata.GenericLiveData; import org.sufficientlysecure.keychain.livedata.PgpKeyGenerationLiveData; import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.daos.KeyRepository; import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectIdentityKeyPresenter.RemoteSelectIdentityKeyView; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.MainActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper.AbstractCallback; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; -import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; @@ -246,7 +243,7 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { @NonNull private RemoteSelectIdentityKeyView createMvpView(final ViewGroup rootView, LayoutInflater layoutInflater) { // final ImageView iconClientApp = rootView.findViewById(R.id.icon_client_app); - final KeyChoiceAdapter keyChoiceAdapter = new KeyChoiceAdapter(layoutInflater); + final DialogKeyChoiceAdapter keyChoiceAdapter = new DialogKeyChoiceAdapter(requireContext(), layoutInflater); final TextView titleText = rootView.findViewById(R.id.text_title_select_key); final TextView addressText = rootView.findViewById(R.id.text_user_id); final TextView autocryptHint = rootView.findViewById(R.id.key_import_autocrypt_hint); @@ -446,91 +443,6 @@ public class RemoteSelectIdKeyActivity extends FragmentActivity { importOpHelper.cryptoOperation(); } - private static class KeyChoiceAdapter extends Adapter { - private final LayoutInflater layoutInflater; - private List data; - private Drawable iconUnselected; - private Drawable iconSelected; - private Integer activeItem; - - KeyChoiceAdapter(LayoutInflater layoutInflater) { - this.layoutInflater = layoutInflater; - } - - @NonNull - @Override - public KeyChoiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View keyChoiceItemView = layoutInflater.inflate(R.layout.api_select_identity_item, parent, false); - return new KeyChoiceViewHolder(keyChoiceItemView); - } - - void setActiveItem(Integer activeItem) { - this.activeItem = activeItem; - notifyDataSetChanged(); - } - - @Override - public void onBindViewHolder(@NonNull KeyChoiceViewHolder holder, int position) { - UnifiedKeyInfo keyInfo = data.get(position); - boolean hasActiveItem = activeItem != null; - boolean isActiveItem = hasActiveItem && position == activeItem; - - Drawable icon = isActiveItem ? iconSelected : iconUnselected; - holder.bind(keyInfo, icon); - - holder.itemView.setVisibility(!hasActiveItem || isActiveItem ? View.VISIBLE : View.INVISIBLE); - } - - @Override - public int getItemCount() { - return data != null ? data.size() : 0; - } - - public void setData(List data) { - this.data = data; - notifyDataSetChanged(); - } - - void setSelectionDrawables(Drawable iconSelected, Drawable iconUnselected) { - this.iconSelected = iconSelected; - this.iconUnselected = iconUnselected; - - notifyDataSetChanged(); - } - } - - private static class KeyChoiceViewHolder extends RecyclerView.ViewHolder { - private final TextView vName; - private final TextView vCreation = (TextView) itemView.findViewById(R.id.key_list_item_creation); - private final ImageView vIcon; - - KeyChoiceViewHolder(View itemView) { - super(itemView); - - vName = itemView.findViewById(R.id.key_list_item_name); - vIcon = itemView.findViewById(R.id.key_list_item_icon); - } - - void bind(UnifiedKeyInfo keyInfo, Drawable selectionIcon) { - Context context = vCreation.getContext(); - - String email = keyInfo.email(); - String name = keyInfo.name(); - if (email != null) { - vName.setText(context.getString(R.string.use_key, email)); - } else if (name != null) { - vName.setText(context.getString(R.string.use_key, name)); - } else { - vName.setText(context.getString(R.string.use_key_no_name)); - } - - KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); - keyInfoFormatter.formatCreationDate(vCreation); - - vIcon.setImageDrawable(selectionIcon); - } - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (importOpHelper.handleActivityResult(requestCode, resultCode, data)) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java index 7be6d945a..1e15a1e84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DebugActionsActivity.java @@ -110,6 +110,11 @@ public class DebugActionsActivity extends Activity { new Intent(), BuildConfig.APPLICATION_ID, getPackageSig(), "test@openkeychain.org", false); startPendingIntent(pendingIntent); }); + addButtonToLayout(context, verticalLayout, "Select Authentication Key").setOnClickListener((v) -> { + PendingIntent pendingIntent = pendingIntentFactory.createSelectAuthenticationKeyIdPendingIntent( + new Intent(), BuildConfig.APPLICATION_ID); + startPendingIntent(pendingIntent); + }); addButtonToLayout(context, verticalLayout, "Select Signing Key (Autocrypt)").setOnClickListener((v) -> { PendingIntent pendingIntent = pendingIntentFactory.createSelectSignKeyIdPendingIntent( new Intent(), BuildConfig.APPLICATION_ID, getPackageSig(), "test@openkeychain.org", true); 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 68da58389..72b97b352 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -75,6 +75,7 @@ import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; import org.sufficientlysecure.keychain.ui.keyview.GenericViewModel; import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity; +import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FabContainer; @@ -141,6 +142,7 @@ public class KeyListFragment extends RecyclerFragment { @@ -34,41 +34,40 @@ public class KeyChoiceAdapter extends FlexibleAdapter { private final KeyDisabledPredicate keyDisabledPredicate; @Nullable private Integer activeItem; + private KeyInfoFormatter keyInfoFormatter; - public static KeyChoiceAdapter createSingleClickableAdapter(List items, - OnKeyClickListener onKeyClickListener) { - return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE, null); + public static KeyChoiceAdapter createSingleClickableAdapter(Context context, List items, + OnKeyClickListener onKeyClickListener, + KeyDisabledPredicate keyDisabledPredicate) { + return new KeyChoiceAdapter(context, items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE, + keyDisabledPredicate + ); } - public static KeyChoiceAdapter createSingleClickableAdapter(List items, - OnKeyClickListener onKeyClickListener, - KeyDisabledPredicate keyDisabledPredicate) { - return new KeyChoiceAdapter(items, Objects.requireNonNull(onKeyClickListener), Mode.IDLE, keyDisabledPredicate); + public static KeyChoiceAdapter createSingleChoiceAdapter(Context context, List items, + KeyDisabledPredicate keyDisabledPredicate) { + return new KeyChoiceAdapter(context, items, null, Mode.SINGLE, keyDisabledPredicate); } - public static KeyChoiceAdapter createSingleChoiceAdapter(List items) { - return new KeyChoiceAdapter(items, null, Mode.SINGLE, null); + public static KeyChoiceAdapter createMultiChoiceAdapter(Context context, List items, + KeyDisabledPredicate keyDisabledPredicate) { + return new KeyChoiceAdapter(context, items, null, Mode.MULTI, keyDisabledPredicate); } - public static KeyChoiceAdapter createSingleChoiceAdapter(List items, KeyDisabledPredicate keyDisabledPredicate) { - return new KeyChoiceAdapter(items, null, Mode.SINGLE, keyDisabledPredicate); - } - - public static KeyChoiceAdapter createMultiChoiceAdapter(List items, KeyDisabledPredicate keyDisabledPredicate) { - return new KeyChoiceAdapter(items, null, Mode.MULTI, keyDisabledPredicate); - } - - private KeyChoiceAdapter(List items, @Nullable OnKeyClickListener onKeyClickListener, int idle, + private KeyChoiceAdapter(Context context, List items, + @Nullable OnKeyClickListener onKeyClickListener, int idle, @Nullable KeyDisabledPredicate keyDisabledPredicate) { - super(getKeyChoiceItems(items, keyDisabledPredicate)); + super(null, null, true); setMode(idle); addListener((OnItemClickListener) (view, position) -> onClickItem(position)); + updateDataSet(getKeyChoiceItems(items, keyDisabledPredicate), false); + this.keyInfoFormatter = new KeyInfoFormatter(context); this.onKeyClickListener = onKeyClickListener; this.keyDisabledPredicate = keyDisabledPredicate; } @Nullable - private static ArrayList getKeyChoiceItems(@Nullable List items, + private ArrayList getKeyChoiceItems(@Nullable List items, @Nullable KeyDisabledPredicate keyDisabledPredicate) { if (items == null) { return null; @@ -174,7 +173,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { return result; } - public static class KeyChoiceItem extends AbstractFlexibleItem { + public class KeyChoiceItem extends AbstractFlexibleItem { private UnifiedKeyInfo keyInfo; @StringRes private Integer disabledStringRes; @@ -220,7 +219,7 @@ public class KeyChoiceAdapter extends FlexibleAdapter { } } - public static class KeyChoiceViewHolder extends FlexibleViewHolder { + public class KeyChoiceViewHolder extends FlexibleViewHolder { private final TextView vName; private final TextView vCreation; private final CheckBox vCheckbox; @@ -236,9 +235,10 @@ public class KeyChoiceAdapter extends FlexibleAdapter { } void bind(UnifiedKeyInfo keyInfo, int choiceMode, boolean isActive, boolean isEnabled) { + keyInfoFormatter.setKeyInfo(keyInfo); + vName.setText(keyInfo.user_id()); - KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(itemView.getContext(), keyInfo, null); keyInfoFormatter.formatCreationDate(vCreation); switch (choiceMode) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java index 3f6aea805..c88d31aac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/bindings/ImportKeysBindingsUtils.java @@ -31,7 +31,8 @@ public class ImportKeysBindingsUtils { public static Highlighter getHighlighter(Context context, String query) { Highlighter highlighter = highlighterCache.get(query); if (highlighter == null) { - highlighter = new Highlighter(context, query); + highlighter = new Highlighter(context); + highlighter.setQuery(query); highlighterCache.put(query, highlighter); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java index 069019c80..3ca48d72b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/chips/EncryptRecipientDropdownAdapter.java @@ -21,11 +21,13 @@ import org.sufficientlysecure.keychain.ui.util.KeyInfoFormatter; public class EncryptRecipientDropdownAdapter extends ChipsInput.ChipDropdownAdapter { private final LayoutInflater layoutInflater; + private final KeyInfoFormatter keyInfoFormatter; EncryptRecipientDropdownAdapter(Context context, List keyInfoChips) { super(keyInfoChips); layoutInflater = LayoutInflater.from(context); + keyInfoFormatter = new KeyInfoFormatter(context); } class ItemViewHolder extends RecyclerView.ViewHolder { @@ -42,6 +44,14 @@ public class EncryptRecipientDropdownAdapter extends ChipsInput.ChipDropdownAdap vStatusIcon = itemView.findViewById(R.id.key_list_item_status_icon); vCreationDate = itemView.findViewById(R.id.key_list_item_creation); } + + public void bind(EncryptRecipientChip chip) { + keyInfoFormatter.setKeyInfo(chip.keyInfo); + + keyInfoFormatter.formatUserId(vMainUserId, vMainUserIdRest); + keyInfoFormatter.formatCreationDate(vCreationDate); + keyInfoFormatter.formatStatusIcon(vStatusIcon); + } } @NonNull @@ -54,10 +64,6 @@ public class EncryptRecipientDropdownAdapter extends ChipsInput.ChipDropdownAdap @Override public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { EncryptRecipientChip chip = getItem(position); - - KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(layoutInflater.getContext(), chip.keyInfo, null); - keyInfoFormatter.formatUserId(holder.vMainUserId, holder.vMainUserIdRest); - keyInfoFormatter.formatCreationDate(holder.vCreationDate); - keyInfoFormatter.formatStatusIcon(holder.vStatusIcon); + holder.bind(chip); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java index e1488e09d..39ea04187 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java @@ -30,9 +30,12 @@ public class Highlighter { private Context mContext; private String mQuery; - public Highlighter(Context context, String query) { + public Highlighter(Context context) { mContext = context; - mQuery = query; + } + + public void setQuery(String mQuery) { + this.mQuery = mQuery; } public Spannable highlight(CharSequence text) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java index ab82d025f..940f01776 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyInfoFormatter.java @@ -11,6 +11,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.model.SubKey; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import java.util.List; @@ -19,13 +20,20 @@ public class KeyInfoFormatter { private static final long JUST_NOW_THRESHOLD = DateUtils.MINUTE_IN_MILLIS * 5; private Context context; - private SubKey.UnifiedKeyInfo keyInfo; private Highlighter highlighter; + private UnifiedKeyInfo keyInfo; - public KeyInfoFormatter(Context context, SubKey.UnifiedKeyInfo keyInfo, String highlightString) { + public KeyInfoFormatter(Context context) { this.context = context; + highlighter = new Highlighter(context); + } + + public void setKeyInfo(UnifiedKeyInfo keyInfo) { this.keyInfo = keyInfo; - highlighter = new Highlighter(context, highlightString); + } + + public void setHighlightString(String highlight) { + highlighter.setQuery(highlight); } public void formatUserId(TextView name, TextView email) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java index 570336050..b41b7adc8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyChoiceSpinnerAdapter.java @@ -19,14 +19,17 @@ import java.util.List; class KeyChoiceSpinnerAdapter extends BaseAdapter { + private final LayoutInflater layoutInflater; + private final KeyInfoFormatter keyInfoFormatter; + private Integer noneItemString; private List data; - private final LayoutInflater layoutInflater; KeyChoiceSpinnerAdapter(Context context) { super(); layoutInflater = LayoutInflater.from(context); + keyInfoFormatter = new KeyInfoFormatter(context); } public void setData(List data) { @@ -114,28 +117,26 @@ class KeyChoiceSpinnerAdapter extends BaseAdapter { } UnifiedKeyInfo keyInfo = getItem(position); - viewHolder.bind(view.getContext(), keyInfo, isEnabled(position)); + viewHolder.bind(keyInfo, isEnabled(position)); return view; } - public static class KeyChoiceViewHolder { - private View mView; + public class KeyChoiceViewHolder { private TextView mMainUserId; private TextView mMainUserIdRest; private TextView mCreationDate; private ImageView mStatus; KeyChoiceViewHolder(View view) { - mView = view; mMainUserId = view.findViewById(R.id.key_list_item_name); mMainUserIdRest = view.findViewById(R.id.key_list_item_email); mStatus = view.findViewById(R.id.key_list_item_status_icon); mCreationDate = view.findViewById(R.id.key_list_item_creation); } - public void bind(Context context, UnifiedKeyInfo keyInfo, boolean enabled) { - KeyInfoFormatter keyInfoFormatter = new KeyInfoFormatter(context, keyInfo, null); + public void bind(UnifiedKeyInfo keyInfo, boolean enabled) { + keyInfoFormatter.setKeyInfo(keyInfo); keyInfoFormatter.formatUserId(mMainUserId, mMainUserIdRest); keyInfoFormatter.formatCreationDate(mCreationDate); keyInfoFormatter.formatStatusIcon(mStatus); diff --git a/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml b/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml index 953d05ba1..3246f1801 100644 --- a/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml +++ b/OpenKeychain/src/main/res/layout/api_remote_select_authentication_key.xml @@ -85,7 +85,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/authentication_key_list" - tools:listitem="@layout/authentication_key_item" + tools:listitem="@layout/key_choice_item" tools:layout_height="100dp" /> diff --git a/OpenKeychain/src/main/res/layout/authentication_key_item.xml b/OpenKeychain/src/main/res/layout/authentication_key_item.xml deleted file mode 100644 index 5e8ed1477..000000000 --- a/OpenKeychain/src/main/res/layout/authentication_key_item.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - diff --git a/OpenKeychain/src/main/res/layout/key_choice_item.xml b/OpenKeychain/src/main/res/layout/key_choice_item.xml index f808b08ab..2f176fac2 100644 --- a/OpenKeychain/src/main/res/layout/key_choice_item.xml +++ b/OpenKeychain/src/main/res/layout/key_choice_item.xml @@ -6,7 +6,9 @@ android:paddingTop="6dp" android:paddingBottom="6dp" android:gravity="center_vertical" - android:orientation="horizontal"> + android:orientation="horizontal" + android:background="?selectableItemBackground" + android:minHeight="?listPreferredItemHeight"> Date: Wed, 4 Jul 2018 21:57:52 +0200 Subject: [PATCH 121/124] don't include revoked user packets in search string --- .../java/org/sufficientlysecure/keychain/KeychainDatabase.java | 3 +++ .../main/sqldelight/org/sufficientlysecure/keychain/Keys.sq | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java index a9da8f018..f8ae8bf5d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java @@ -103,6 +103,9 @@ public class KeychainDatabase { if (!db.isReadOnly()) { // Enable foreign key constraints db.execSQL("PRAGMA foreign_keys=ON;"); + if (Constants.DEBUG) { + recreateUnifiedKeyView(db); + } } } }).build()); diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index afa21ed90..dcf5cc586 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -34,7 +34,7 @@ CREATE VIEW unifiedKeyView AS GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list FROM keys - INNER JOIN user_packets ON ( keys.master_key_id = user_packets.master_key_id AND user_packets.type IS NULL ) + INNER JOIN user_packets ON ( keys.master_key_id = user_packets.master_key_id AND user_packets.type IS NULL AND (user_packets.rank = 0 OR user_packets.is_revoked = 0)) LEFT JOIN certs ON ( keys.master_key_id = certs.master_key_id AND certs.verified = 1 ) LEFT JOIN autocrypt_peers AS aTI ON ( aTI.master_key_id = keys.master_key_id ) WHERE keys.rank = 0 From 5b4f6cebf7306396ff0830e3a1eedb5e40b8f614 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 22:09:52 +0200 Subject: [PATCH 122/124] always reset singleton if running a unit test --- .../org/sufficientlysecure/keychain/Constants.java | 10 ++++++++++ .../sufficientlysecure/keychain/KeychainDatabase.java | 7 ++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index fce89989b..c3b2687de 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -37,6 +37,8 @@ public final class Constants { public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false; public static final boolean DEBUG_KEYSERVER_SYNC = false; + public static final boolean IS_RUNNING_UNITTEST = isRunningUnitTest(); + public static final String TAG = DEBUG ? "Keychain D" : "Keychain"; public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain"; @@ -211,4 +213,12 @@ public final class Constants { public static final KeyFormat SECURITY_TOKEN_V2_DEC = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); public static final KeyFormat SECURITY_TOKEN_V2_AUTH = new RSAKeyFormat(2048, ELEN, RSAKeyFormat.RSAAlgorithmFormat.CRT_WITH_MODULUS); + private static boolean isRunningUnitTest() { + try { + Class.forName("org.sufficientlysecure.keychain.KeychainTestRunner"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java index f8ae8bf5d..cbcf7e451 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainDatabase.java @@ -58,15 +58,12 @@ public class KeychainDatabase { private static KeychainDatabase sInstance; public static KeychainDatabase getInstance(Context context) { + if (sInstance == null || Constants.IS_RUNNING_UNITTEST) { sInstance = new KeychainDatabase(context.getApplicationContext()); + } return sInstance; } - @VisibleForTesting - public static void resetSingleton() { - sInstance = null; - } - public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; String KEYS = "keys"; From 37040f8586aab6edb639192e3b954d4ce2ddcced Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 22:20:30 +0200 Subject: [PATCH 123/124] update "last seen" when uploading to keyserver --- .../keychain/operations/EditKeyOperation.java | 2 +- .../keychain/operations/UploadOperation.java | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) 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 3b101bc1c..f0d10c7b5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -171,7 +171,7 @@ public class EditKeyOperation extends BaseReadWriteOperation SaveKeyringResult saveResult = mKeyWritableRepository.saveSecretKeyRing(ring); log.add(saveResult, 1); - if (isNewKey) { + if (isNewKey || saveParcel.isShouldUpload()) { keyMetadataDao.renewKeyLastUpdatedTime(ring.getMasterKeyId(), saveParcel.isShouldUpload()); } 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 93e8b912f..221d7af64 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java @@ -29,6 +29,9 @@ import android.support.v4.os.CancellationSignal; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.daos.KeyMetadataDao; +import org.sufficientlysecure.keychain.daos.KeyRepository; +import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient; import org.sufficientlysecure.keychain.keyimport.KeyserverClient.AddKeyException; @@ -41,8 +44,6 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.Progressable; import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.daos.KeyRepository; -import org.sufficientlysecure.keychain.daos.KeyWritableRepository; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; @@ -56,10 +57,13 @@ import timber.log.Timber; * An operation class which implements the upload of a single key to a key server. */ public class UploadOperation extends BaseOperation { + private KeyMetadataDao keyMetadataDao; public UploadOperation(Context context, KeyRepository keyRepository, Progressable progressable, CancellationSignal cancelled) { super(context, keyRepository, progressable, cancelled); + + keyMetadataDao = KeyMetadataDao.create(mContext); } @NonNull @@ -150,11 +154,18 @@ public class UploadOperation extends BaseOperation { keyring.encode(aos); aos.close(); + if (checkCancelled()) { + log.add(LogType.MSG_OPERATION_CANCELLED, 0); + return new UploadResult(UploadResult.RESULT_CANCELLED, log); + } + String armoredKey = bos.toString("UTF-8"); keyserverInteractor.add(armoredKey, proxy); updateProgress(R.string.progress_uploading, 1, 1); + keyMetadataDao.renewKeyLastUpdatedTime(keyring.getMasterKeyId(), true); + log.add(LogType.MSG_UPLOAD_SUCCESS, 1); return new UploadResult(UploadResult.RESULT_OK, log); } catch (IOException e) { From 1fad244c7a437fcadf63488336c50660b1918a24 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 4 Jul 2018 22:44:09 +0200 Subject: [PATCH 124/124] Correctly set keyserver status to unknown if importing from file --- .../keychain/operations/ImportOperation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8612e33f7..e1c1db5cf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java @@ -249,8 +249,8 @@ public class ImportOperation extends BaseReadWriteOperation importedMasterKeyIds.add(key.getMasterKeyId()); } - if (!skipSave) { - keyMetadataDao.renewKeyLastUpdatedTime(key.getMasterKeyId(), keyWasDownloaded); + if (!skipSave && keyWasDownloaded) { + keyMetadataDao.renewKeyLastUpdatedTime(key.getMasterKeyId(), true); } }