From 10d3ca814c0263c0aad37be3df7773ba0e4bc00b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 25 Mar 2018 15:09:41 +0200 Subject: [PATCH 1/2] Use LiveData in favor of ContentLoader for ViewKeyFragment --- .../keychain/ui/adapter/IdentityAdapter.java | 8 +- .../keychain/ui/keyview/ViewKeyFragment.java | 68 ++++++++++--- .../{IdentityLoader.java => IdentityDao.java} | 91 +++++------------ .../keyview/loader/KeyserverStatusLoader.java | 59 ++--------- ...StatusLoader.java => SubkeyStatusDao.java} | 52 +++------- ...tInfoLoader.java => SystemContactDao.java} | 61 +++++------- .../ui/keyview/loader/ViewKeyLiveData.java | 98 +++++++++++++++++++ .../presenter/IdentitiesPresenter.java | 45 +++------ .../keyview/presenter/KeyHealthPresenter.java | 47 ++++----- .../presenter/KeyserverStatusPresenter.java | 36 +++---- .../presenter/SystemContactPresenter.java | 66 ++++--------- 11 files changed, 291 insertions(+), 340 deletions(-) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/{IdentityLoader.java => IdentityDao.java} (80%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/{SubkeyStatusLoader.java => SubkeyStatusDao.java} (81%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/{SystemContactInfoLoader.java => SystemContactDao.java} (76%) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.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 b02b61b5c..f3bb61d51 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 @@ -37,10 +37,10 @@ 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.IdentityLoader.IdentityInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.LinkedIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.AutocryptPeerInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.UserIdInfo; +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; 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 27c04e1ae..b1e675583 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 @@ -18,6 +18,11 @@ 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.Intent; import android.os.Bundle; import android.os.Handler; @@ -36,6 +41,10 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; 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; import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyHealthPresenter; import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter; @@ -53,11 +62,6 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O boolean mIsSecret = false; - private static final int LOADER_IDENTITIES = 1; - private static final int LOADER_ID_LINKED_CONTACT = 2; - private static final int LOADER_ID_SUBKEY_STATUS = 3; - private static final int LOADER_ID_KEYSERVER_STATUS = 4; - private IdentitiesCardView identitiesCardView; private IdentitiesPresenter identitiesPresenter; @@ -100,6 +104,41 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O return root; } + 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); @@ -107,21 +146,22 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O long masterKeyId = getArguments().getLong(ARG_MASTER_KEY_ID); mIsSecret = getArguments().getBoolean(ARG_IS_SECRET); + KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class); + identitiesPresenter = new IdentitiesPresenter( - getContext(), identitiesCardView, this, LOADER_IDENTITIES, masterKeyId, mIsSecret); - identitiesPresenter.startLoader(getLoaderManager()); + getContext(), identitiesCardView, this, masterKeyId, mIsSecret); + model.getIdentityInfo(identitiesPresenter).observe(this, identitiesPresenter); systemContactPresenter = new SystemContactPresenter( - getContext(), systemContactCard, LOADER_ID_LINKED_CONTACT, masterKeyId, mIsSecret); - systemContactPresenter.startLoader(getLoaderManager()); + getContext(), systemContactCard, masterKeyId, mIsSecret); + model.getSystemContactInfo(systemContactPresenter).observe(this, systemContactPresenter); - keyHealthPresenter = new KeyHealthPresenter( - getContext(), keyStatusHealth, LOADER_ID_SUBKEY_STATUS, masterKeyId, mIsSecret); - keyHealthPresenter.startLoader(getLoaderManager()); + keyHealthPresenter = new KeyHealthPresenter(getContext(), keyStatusHealth, masterKeyId); + model.getSubkeyStatus(keyHealthPresenter).observe(this, keyHealthPresenter); keyserverStatusPresenter = new KeyserverStatusPresenter( - getContext(), keyStatusKeyserver, LOADER_ID_KEYSERVER_STATUS, masterKeyId, mIsSecret); - keyserverStatusPresenter.startLoader(getLoaderManager()); + getContext(), keyStatusKeyserver, masterKeyId, mIsSecret); + model.getKeyserverStatus(keyserverStatusPresenter).observe(this, keyserverStatusPresenter); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java similarity index 80% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java index bafd1e74b..8a3458a51 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/IdentityDao.java @@ -26,11 +26,11 @@ import java.util.List; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; -import android.support.v4.content.AsyncTaskLoader; import com.google.auto.value.AutoValue; import org.openintents.openpgp.util.OpenPgpApi; @@ -38,14 +38,12 @@ import org.sufficientlysecure.keychain.linked.LinkedAttribute; import org.sufficientlysecure.keychain.linked.UriAttribute; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.IdentityInfo; import org.sufficientlysecure.keychain.ui.util.PackageIconGetter; import timber.log.Timber; -public class IdentityLoader extends AsyncTaskLoader> { +public class IdentityDao { private static final String[] USER_PACKETS_PROJECTION = new String[]{ UserPackets._ID, UserPackets.TYPE, @@ -84,41 +82,34 @@ public class IdentityLoader extends AsyncTaskLoader> { private final ContentResolver contentResolver; private final PackageIconGetter packageIconGetter; - private final long masterKeyId; - private final boolean showLinkedIds; + private final PackageManager packageManager; - private List cachedResult; - - private ForceLoadContentObserver identityObserver; - - - public IdentityLoader(Context context, ContentResolver contentResolver, long masterKeyId, boolean showLinkedIds) { - super(context); - - this.contentResolver = contentResolver; - this.masterKeyId = masterKeyId; - this.showLinkedIds = showLinkedIds; - - this.identityObserver = new ForceLoadContentObserver(); - this.packageIconGetter = PackageIconGetter.getInstance(context); - - this.identityObserver = new ForceLoadContentObserver(); + static IdentityDao getInstance(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + PackageManager packageManager = context.getPackageManager(); + PackageIconGetter iconGetter = PackageIconGetter.getInstance(context); + return new IdentityDao(contentResolver, packageManager, iconGetter); } - @Override - public List loadInBackground() { + private IdentityDao(ContentResolver contentResolver, PackageManager packageManager, PackageIconGetter iconGetter) { + this.packageManager = packageManager; + this.contentResolver = contentResolver; + this.packageIconGetter = iconGetter; + } + + List getIdentityInfos(long masterKeyId, boolean showLinkedIds) { ArrayList identities = new ArrayList<>(); if (showLinkedIds) { - loadLinkedIds(identities); + loadLinkedIds(identities, masterKeyId); } - loadUserIds(identities); - correlateOrAddAutocryptPeers(identities); + loadUserIds(identities, masterKeyId); + correlateOrAddAutocryptPeers(identities, masterKeyId); return Collections.unmodifiableList(identities); } - private void correlateOrAddAutocryptPeers(ArrayList identities) { + private void correlateOrAddAutocryptPeers(ArrayList identities, long masterKeyId) { Cursor cursor = contentResolver.query(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId), AUTOCRYPT_PEER_PROJECTION, null, null, null); if (cursor == null) { @@ -158,7 +149,7 @@ public class IdentityLoader extends AsyncTaskLoader> { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, autocryptPeer); - List resolveInfos = getContext().getPackageManager().queryIntentActivities(intent, 0); + List resolveInfos = packageManager.queryIntentActivities(intent, 0); if (resolveInfos != null && !resolveInfos.isEmpty()) { return intent; } else { @@ -178,7 +169,7 @@ public class IdentityLoader extends AsyncTaskLoader> { return null; } - private void loadLinkedIds(ArrayList identities) { + 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) { @@ -208,7 +199,7 @@ public class IdentityLoader extends AsyncTaskLoader> { } } - private void loadUserIds(ArrayList identities) { + 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) { @@ -236,36 +227,6 @@ public class IdentityLoader extends AsyncTaskLoader> { } } - @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(); - } - - getContext().getContentResolver().registerContentObserver( - KeyRings.buildGenericKeyRingUri(masterKeyId), true, identityObserver); - } - - @Override - protected void onAbandon() { - super.onAbandon(); - - getContext().getContentResolver().unregisterContentObserver(identityObserver); - } - public interface IdentityInfo { int getRank(); int getVerified(); @@ -287,7 +248,7 @@ public class IdentityLoader extends AsyncTaskLoader> { static UserIdInfo create(int rank, int verified, boolean isPrimary, String name, String email, String comment) { - return new AutoValue_IdentityLoader_UserIdInfo(rank, verified, isPrimary, name, email, comment); + return new AutoValue_IdentityDao_UserIdInfo(rank, verified, isPrimary, name, email, comment); } } @@ -300,7 +261,7 @@ public class IdentityLoader extends AsyncTaskLoader> { public abstract UriAttribute getUriAttribute(); static LinkedIdInfo create(int rank, int verified, boolean isPrimary, UriAttribute uriAttribute) { - return new AutoValue_IdentityLoader_LinkedIdInfo(rank, verified, isPrimary, uriAttribute); + return new AutoValue_IdentityDao_LinkedIdInfo(rank, verified, isPrimary, uriAttribute); } } @@ -321,12 +282,12 @@ public class IdentityLoader extends AsyncTaskLoader> { static AutocryptPeerInfo create(UserIdInfo userIdInfo, String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityLoader_AutocryptPeerInfo(userIdInfo.getRank(), userIdInfo.getVerified(), + return new AutoValue_IdentityDao_AutocryptPeerInfo(userIdInfo.getRank(), userIdInfo.getVerified(), userIdInfo.isPrimary(), autocryptPeer, packageName, appIcon, userIdInfo, autocryptPeerIntent); } static AutocryptPeerInfo create(String autocryptPeer, String packageName, Drawable appIcon, Intent autocryptPeerIntent) { - return new AutoValue_IdentityLoader_AutocryptPeerInfo( + return new AutoValue_IdentityDao_AutocryptPeerInfo( 0, Certs.VERIFIED_SELF, false, autocryptPeer, packageName, appIcon, null, autocryptPeerIntent); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java index 5f643ee1c..16e6828c6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java @@ -23,17 +23,12 @@ import java.util.Date; 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.provider.KeychainContract.UpdatedKeys; -import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusLoader.KeyserverStatus; import timber.log.Timber; -public class KeyserverStatusLoader extends AsyncTaskLoader { +public class KeyserverStatusDao { public static final String[] PROJECTION = new String[] { UpdatedKeys.LAST_UPDATED, UpdatedKeys.SEEN_ON_KEYSERVERS @@ -43,23 +38,17 @@ public class KeyserverStatusLoader extends AsyncTaskLoader { private final ContentResolver contentResolver; - private final long masterKeyId; - private KeyserverStatus cachedResult; - - private ForceLoadContentObserver keyserverStatusObserver; - - public KeyserverStatusLoader(Context context, ContentResolver contentResolver, long masterKeyId) { - super(context); - - this.contentResolver = contentResolver; - this.masterKeyId = masterKeyId; - - this.keyserverStatusObserver = new ForceLoadContentObserver(); + public static KeyserverStatusDao getInstance(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + return new KeyserverStatusDao(contentResolver); } - @Override - public KeyserverStatus loadInBackground() { + private KeyserverStatusDao(ContentResolver contentResolver) { + this.contentResolver = contentResolver; + } + + public KeyserverStatus getKeyserverStatus(long masterKeyId) { Cursor cursor = contentResolver.query(UpdatedKeys.CONTENT_URI, PROJECTION, UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(masterKeyId) }, null); if (cursor == null) { @@ -85,36 +74,6 @@ public class KeyserverStatusLoader extends AsyncTaskLoader { } } - @Override - public void deliverResult(KeyserverStatus keySubkeyStatus) { - cachedResult = keySubkeyStatus; - - if (isStarted()) { - super.deliverResult(keySubkeyStatus); - } - } - - @Override - protected void onStartLoading() { - if (cachedResult != null) { - deliverResult(cachedResult); - } - - if (takeContentChanged() || cachedResult == null) { - forceLoad(); - } - - getContext().getContentResolver().registerContentObserver( - KeyRings.buildGenericKeyRingUri(masterKeyId), true, keyserverStatusObserver); - } - - @Override - protected void onAbandon() { - super.onAbandon(); - - getContext().getContentResolver().unregisterContentObserver(keyserverStatusObserver); - } - public static class KeyserverStatus { private final long masterKeyId; private final boolean isPublished; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java similarity index 81% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusLoader.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java index 82816d05d..8caf8b261 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SubkeyStatusDao.java @@ -28,19 +28,15 @@ import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.support.annotation.NonNull; -import android.support.v4.content.AsyncTaskLoader; -import android.util.Log; -import org.sufficientlysecure.keychain.Constants; 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 org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusLoader.KeySubkeyStatus; import timber.log.Timber; -public class SubkeyStatusLoader extends AsyncTaskLoader { +public class SubkeyStatusDao { public static final String[] PROJECTION = new String[] { Keys.KEY_ID, Keys.CREATION, @@ -68,23 +64,18 @@ public class SubkeyStatusLoader extends AsyncTaskLoader { private final ContentResolver contentResolver; - private final long masterKeyId; - private final Comparator comparator; - - private KeySubkeyStatus cachedResult; - public SubkeyStatusLoader(Context context, ContentResolver contentResolver, long masterKeyId, - Comparator comparator) { - super(context); - - this.contentResolver = contentResolver; - this.masterKeyId = masterKeyId; - this.comparator = comparator; + public static SubkeyStatusDao getInstance(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + return new SubkeyStatusDao(contentResolver); } - @Override - public KeySubkeyStatus loadInBackground() { + private SubkeyStatusDao(ContentResolver contentResolver) { + this.contentResolver = contentResolver; + } + + 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!"); @@ -111,7 +102,10 @@ public class SubkeyStatusLoader extends AsyncTaskLoader { } if (keyCertify == null) { - throw new IllegalStateException("Certification key must be set at this point, it's a bug otherwise!"); + 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); @@ -123,26 +117,6 @@ public class SubkeyStatusLoader extends AsyncTaskLoader { } } - @Override - public void deliverResult(KeySubkeyStatus 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 KeySubkeyStatus { @NonNull public final SubKeyItem keyCertify; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactInfoLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java similarity index 76% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactInfoLoader.java rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java index cfbe51f73..e666920c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactInfoLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/SystemContactDao.java @@ -20,22 +20,22 @@ package org.sufficientlysecure.keychain.ui.keyview.loader; import java.util.List; +import android.Manifest; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.provider.ContactsContract; -import android.support.v4.content.AsyncTaskLoader; -import android.util.Log; +import android.support.v4.content.ContextCompat; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactInfoLoader.SystemContactInfo; import org.sufficientlysecure.keychain.util.ContactHelper; import timber.log.Timber; -public class SystemContactInfoLoader extends AsyncTaskLoader { +public class SystemContactDao { private static final String[] PROJECTION = { ContactsContract.RawContacts.CONTACT_ID }; @@ -43,23 +43,30 @@ public class SystemContactInfoLoader extends AsyncTaskLoader private static final String CONTACT_NOT_DELETED = "0"; + private final Context context; private final ContentResolver contentResolver; - private final long masterKeyId; - private final boolean isSecret; - - private SystemContactInfo cachedResult; + private final ContactHelper contactHelper; - public SystemContactInfoLoader(Context context, ContentResolver contentResolver, long masterKeyId, boolean isSecret) { - super(context); - - this.contentResolver = contentResolver; - this.masterKeyId = masterKeyId; - this.isSecret = isSecret; + public static SystemContactDao getInstance(Context context) { + ContactHelper contactHelper = new ContactHelper(context); + ContentResolver contentResolver = context.getContentResolver(); + return new SystemContactDao(context, contactHelper, contentResolver); } - @Override - public SystemContactInfo loadInBackground() { + private SystemContactDao(Context context, ContactHelper contactHelper, ContentResolver contentResolver) { + this.context = context; + this.contactHelper = contactHelper; + this.contentResolver = contentResolver; + } + + 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!"); + return null; + } + Uri baseUri = isSecret ? ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI : ContactsContract.RawContacts.CONTENT_URI; Cursor cursor = contentResolver.query(baseUri, PROJECTION, @@ -87,8 +94,6 @@ public class SystemContactInfoLoader extends AsyncTaskLoader return null; } - ContactHelper contactHelper = new ContactHelper(getContext()); - String contactName = null; if (isSecret) { //all secret keys are linked to "me" profile in contacts List mainProfileNames = contactHelper.getMainProfileContactName(); @@ -116,26 +121,6 @@ public class SystemContactInfoLoader extends AsyncTaskLoader } } - @Override - public void deliverResult(SystemContactInfo systemContactInfo) { - cachedResult = systemContactInfo; - - if (isStarted()) { - super.deliverResult(systemContactInfo); - } - } - - @Override - protected void onStartLoading() { - if (cachedResult != null) { - deliverResult(cachedResult); - } - - if (takeContentChanged() || cachedResult == null) { - forceLoad(); - } - } - public static class SystemContactInfo { final long masterKeyId; public final long contactId; 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 new file mode 100644 index 000000000..c9bb64dd9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/ViewKeyLiveData.java @@ -0,0 +1,98 @@ +package org.sufficientlysecure.keychain.ui.keyview.loader; + + +import java.util.Comparator; +import java.util.List; + +import android.content.Context; + +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; + + +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 KeyserverStatusDao keyserverStatusDao; + + private final long masterKeyId; + + public KeyserverStatusLiveData(Context context, long masterKeyId) { + super(context, KeyRings.buildGenericKeyRingUri(masterKeyId)); + + this.keyserverStatusDao = KeyserverStatusDao.getInstance(context); + this.masterKeyId = masterKeyId; + } + + @Override + public KeyserverStatus asyncLoadData() { + return keyserverStatusDao.getKeyserverStatus(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 index de4cf3e1e..28cd69b5b 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 @@ -21,13 +21,12 @@ 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.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; +import android.support.annotation.Nullable; import android.view.View; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; @@ -37,21 +36,20 @@ 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.IdentityLoader; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.AutocryptPeerInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.IdentityInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.LinkedIdInfo; -import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityLoader.UserIdInfo; +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 LoaderCallbacks> { +public class IdentitiesPresenter implements Observer> { private final Context context; private final IdentitiesMvpView view; private final ViewKeyMvpView viewKeyMvpView; - private final int loaderId; private final IdentityAdapter identitiesAdapter; @@ -60,11 +58,10 @@ public class IdentitiesPresenter implements LoaderCallbacks> private final boolean showLinkedIds; public IdentitiesPresenter(Context context, IdentitiesMvpView view, ViewKeyMvpView viewKeyMvpView, - int loaderId, long masterKeyId, boolean isSecret) { + long masterKeyId, boolean isSecret) { this.context = context; this.view = view; this.viewKeyMvpView = viewKeyMvpView; - this.loaderId = loaderId; this.masterKeyId = masterKeyId; this.isSecret = isSecret; @@ -90,24 +87,10 @@ public class IdentitiesPresenter implements LoaderCallbacks> view.setIdentitiesCardListener(() -> addLinkedIdentity()); } - public void startLoader(LoaderManager loaderManager) { - loaderManager.restartLoader(loaderId, null, this); - } - @Override - public Loader> onCreateLoader(int id, Bundle args) { - return new IdentityLoader(context, context.getContentResolver(), masterKeyId, showLinkedIds); - } - - @Override - public void onLoadFinished(Loader> loader, List data) { + public void onChanged(@Nullable List identityInfos) { viewKeyMvpView.setContentShown(true, false); - identitiesAdapter.setData(data); - } - - @Override - public void onLoaderReset(Loader loader) { - identitiesAdapter.setData(null); + identitiesAdapter.setData(identityInfos); } private void showIdentityInfo(final int position) { @@ -168,6 +151,10 @@ public class IdentitiesPresenter implements LoaderCallbacks> autocryptPeerDao.delete(info.getIdentity()); } + public LiveData> getLiveDataInstance() { + return new IdentityLiveData(context, masterKeyId, showLinkedIds); + } + public interface IdentitiesMvpView { void setIdentitiesAdapter(IdentityAdapter userIdsAdapter); void setIdentitiesCardListener(IdentitiesCardListener identitiesCardListener); 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 a8bb68552..3a9e11d63 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,21 +21,21 @@ 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; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; +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; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusLoader; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusLoader.KeySubkeyStatus; -import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusLoader.SubKeyItem; -public class KeyHealthPresenter implements LoaderCallbacks { +public class KeyHealthPresenter implements Observer { private static final Comparator SUBKEY_COMPARATOR = new Comparator() { @Override public int compare(SubKeyItem one, SubKeyItem two) { @@ -58,22 +58,16 @@ public class KeyHealthPresenter implements LoaderCallbacks { private final Context context; private final KeyHealthMvpView view; - private final int loaderId; - private final long masterKeyId; - private final boolean isSecret; private KeySubkeyStatus subkeyStatus; private boolean showingExpandedInfo; - public KeyHealthPresenter(Context context, KeyHealthMvpView view, int loaderId, long masterKeyId, boolean isSecret) { + public KeyHealthPresenter(Context context, KeyHealthMvpView view, long masterKeyId) { this.context = context; this.view = view; - this.loaderId = loaderId; - this.masterKeyId = masterKeyId; - this.isSecret = isSecret; view.setOnHealthClickListener(new KeyHealthClickListener() { @Override @@ -83,18 +77,12 @@ public class KeyHealthPresenter implements LoaderCallbacks { }); } - public void startLoader(LoaderManager loaderManager) { - loaderManager.restartLoader(loaderId, null, this); - } - @Override - public Loader onCreateLoader(int id, Bundle args) { - return new SubkeyStatusLoader(context, context.getContentResolver(), masterKeyId, SUBKEY_COMPARATOR); - } - - @Override - public void onLoadFinished(Loader loader, KeySubkeyStatus subkeyStatus) { + public void onChanged(@Nullable KeySubkeyStatus subkeyStatus) { this.subkeyStatus = subkeyStatus; + if (subkeyStatus == null) { + return; + } KeyHealthStatus keyHealthStatus = determineKeyHealthStatus(subkeyStatus); @@ -195,11 +183,6 @@ public class KeyHealthPresenter implements LoaderCallbacks { return KeyHealthStatus.OK; } - @Override - public void onLoaderReset(Loader loader) { - - } - private void onKeyHealthClick() { if (showingExpandedInfo) { showingExpandedInfo = false; @@ -262,6 +245,10 @@ public class KeyHealthPresenter implements LoaderCallbacks { 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 } 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 112144db6..76e63f15e 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 @@ -20,46 +20,43 @@ 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.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; +import android.support.annotation.Nullable; -import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusLoader; -import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusLoader.KeyserverStatus; +import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusDao.KeyserverStatus; +import org.sufficientlysecure.keychain.ui.keyview.loader.ViewKeyLiveData.KeyserverStatusLiveData; -public class KeyserverStatusPresenter implements LoaderCallbacks { +public class KeyserverStatusPresenter implements Observer { private final Context context; private final KeyserverStatusMvpView view; - private final int loaderId; private final long masterKeyId; private final boolean isSecret; - public KeyserverStatusPresenter(Context context, KeyserverStatusMvpView view, int loaderId, long masterKeyId, + public KeyserverStatusPresenter(Context context, KeyserverStatusMvpView view, long masterKeyId, boolean isSecret) { this.context = context; this.view = view; - this.loaderId = loaderId; this.masterKeyId = masterKeyId; this.isSecret = isSecret; } - public void startLoader(LoaderManager loaderManager) { - loaderManager.restartLoader(loaderId, null, this); + public LiveData getLiveDataInstance() { + return new KeyserverStatusLiveData(context, masterKeyId); } @Override - public Loader onCreateLoader(int id, Bundle args) { - return new KeyserverStatusLoader(context, context.getContentResolver(), masterKeyId); - } + public void onChanged(@Nullable KeyserverStatus keyserverStatus) { + if (keyserverStatus == null) { + view.setDisplayStatusUnknown(); + return; + } - @Override - public void onLoadFinished(Loader loader, KeyserverStatus keyserverStatus) { if (keyserverStatus.hasBeenUpdated()) { if (keyserverStatus.isPublished()) { view.setDisplayStatusPublished(); @@ -72,11 +69,6 @@ public class KeyserverStatusPresenter implements LoaderCallbacks { +public class SystemContactPresenter implements Observer { private final Context context; private final SystemContactMvpView view; - private final int loaderId; private final long masterKeyId; private final boolean isSecret; @@ -47,55 +41,29 @@ public class SystemContactPresenter implements LoaderCallbacks getLiveDataInstance() { + return new SystemContactInfoLiveData(context, masterKeyId, isSecret); + } + + @Override + public void onChanged(@Nullable SystemContactInfo systemContactInfo) { + if (systemContactInfo == null) { view.hideLinkedSystemContact(); return; } - Bundle linkedContactData = new Bundle(); - - // initialises loader for contact query so we can listen to any updates - loaderManager.restartLoader(loaderId, linkedContactData, this); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return new SystemContactInfoLoader(context, context.getContentResolver(), masterKeyId, isSecret); - } - - @Override - public void onLoadFinished(Loader loader, SystemContactInfo data) { - if (data == null) { - view.hideLinkedSystemContact(); - return; - } - - this.contactId = data.contactId; - view.showLinkedSystemContact(data.contactName, data.contactPicture); - } - - @Override - public void onLoaderReset(Loader loader) { - + this.contactId = systemContactInfo.contactId; + view.showLinkedSystemContact(systemContactInfo.contactName, systemContactInfo.contactPicture); } private void onSystemContactClick() { From 8adf4a8a64f925610a1156963da33d3fb70a0de1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 14 Jun 2018 15:23:20 +0200 Subject: [PATCH 2/2] move notification of key changes into DAOs --- .../AutocryptPeerDataAccessObject.java | 11 ++- .../provider/DatabaseNotifyManager.java | 42 +++++++++++ .../keychain/provider/KeyRepository.java | 16 ++--- .../provider/KeyWritableRepository.java | 44 +++++++----- .../keychain/provider/KeychainProvider.java | 70 +++---------------- .../provider/LastUpdateInteractor.java | 10 ++- .../remote/KeychainExternalProvider.java | 16 ++++- ...tusLoader.java => KeyserverStatusDao.java} | 0 .../keychain/util/DatabaseUtil.java | 29 ++++++++ .../keychain/util/DatabaseUtils.java | 34 --------- .../keychain/provider/InteropTest.java | 3 +- 11 files changed, 146 insertions(+), 129 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/{KeyserverStatusLoader.java => KeyserverStatusDao.java} (100%) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtils.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java index 32c8c3ee1..d7d4707dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/AutocryptPeerDataAccessObject.java @@ -72,10 +72,11 @@ public class AutocryptPeerDataAccessObject { 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() { @@ -102,9 +103,11 @@ public class AutocryptPeerDataAccessObject { }; } - public AutocryptPeerDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName) { + public AutocryptPeerDataAccessObject(SimpleContentResolverInterface queryInterface, String packageName, + DatabaseNotifyManager databaseNotifyManager) { this.queryInterface = queryInterface; this.packageName = packageName; + this.databaseNotifyManager = databaseNotifyManager; } public Long getMasterKeyIdForAutocryptPeer(String autocryptId) { @@ -190,6 +193,7 @@ public class AutocryptPeerDataAccessObject { 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) { @@ -211,10 +215,13 @@ public class AutocryptPeerDataAccessObject { 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) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java new file mode 100644 index 000000000..b283dd576 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/DatabaseNotifyManager.java @@ -0,0 +1,42 @@ +package org.sufficientlysecure.keychain.provider; + + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; + +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; + + +public class DatabaseNotifyManager { + private ContentResolver contentResolver; + + public static DatabaseNotifyManager create(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + return new DatabaseNotifyManager(contentResolver); + } + + private DatabaseNotifyManager(ContentResolver contentResolver) { + this.contentResolver = contentResolver; + } + + public void notifyKeyChange(long masterKeyId) { + Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + contentResolver.notifyChange(uri, null); + } + + public void notifyAutocryptDelete(String autocryptId, Long masterKeyId) { + Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + contentResolver.notifyChange(uri, null); + } + + public void notifyAutocryptUpdate(String autocryptId, long masterKeyId) { + Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + contentResolver.notifyChange(uri, null); + } + + public void notifyKeyserverStatusChange(long masterKeyId) { + Uri uri = KeyRings.buildGenericKeyRingUri(masterKeyId); + contentResolver.notifyChange(uri, null); + } +} 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 ac71b52b2..2f1847a17 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -53,7 +53,7 @@ public class KeyRepository { public static final int FIELD_TYPE_STRING = 4; public static final int FIELD_TYPE_BLOB = 5; - final ContentResolver mContentResolver; + final ContentResolver contentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; OperationLog mLog; int mIndent; @@ -71,7 +71,7 @@ public class KeyRepository { KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage, OperationLog log, int indent) { - mContentResolver = contentResolver; + this.contentResolver = contentResolver; mLocalPublicKeyStorage = localPublicKeyStorage; mIndent = indent; mLog = log; @@ -121,7 +121,7 @@ public class KeyRepository { private HashMap getGenericData(Uri uri, String[] proj, int[] types, String selection) throws NotFoundException { - Cursor cursor = mContentResolver.query(uri, proj, selection, null, null); + Cursor cursor = contentResolver.query(uri, proj, selection, null, null); try { HashMap result = new HashMap<>(proj.length); @@ -184,7 +184,7 @@ public class KeyRepository { } public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri queryUri) throws NotFoundException { - Cursor cursor = mContentResolver.query(queryUri, + Cursor cursor = contentResolver.query(queryUri, new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED }, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { @@ -208,7 +208,7 @@ public class KeyRepository { } public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri queryUri) throws NotFoundException { - Cursor cursor = mContentResolver.query(queryUri, + 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()) { @@ -232,7 +232,7 @@ public class KeyRepository { } public ArrayList getConfirmedUserIds(long masterKeyId) throws NotFoundException { - Cursor cursor = mContentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), + Cursor cursor = contentResolver.query(UserPackets.buildUserIdsUri(masterKeyId), new String[]{UserPackets.USER_ID}, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null ); if (cursor == null) { @@ -276,12 +276,12 @@ public class KeyRepository { } public ContentResolver getContentResolver() { - return mContentResolver; + return contentResolver; } @Nullable Long getLastUpdateTime(long masterKeyId) { - Cursor lastUpdatedCursor = mContentResolver.query( + Cursor lastUpdatedCursor = contentResolver.query( UpdatedKeys.CONTENT_URI, new String[] { UpdatedKeys.LAST_UPDATED }, UpdatedKeys.MASTER_KEY_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 9aff7d75a..373332d26 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyWritableRepository.java @@ -83,32 +83,38 @@ import timber.log.Timber; public class KeyWritableRepository extends KeyRepository { private static final int MAX_CACHED_KEY_SIZE = 1024 * 50; - private final Context mContext; + private final Context context; private final LastUpdateInteractor lastUpdateInteractor; + private DatabaseNotifyManager databaseNotifyManager; public static KeyWritableRepository create(Context context) { LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); LastUpdateInteractor lastUpdateInteractor = LastUpdateInteractor.create(context); + DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context); - return new KeyWritableRepository(context, localPublicKeyStorage, lastUpdateInteractor); + return new KeyWritableRepository(context, localPublicKeyStorage, lastUpdateInteractor, + databaseNotifyManager); } @VisibleForTesting KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, - LastUpdateInteractor lastUpdateInteractor) { - this(context, localPublicKeyStorage, lastUpdateInteractor, new OperationLog(), 0); + LastUpdateInteractor lastUpdateInteractor, DatabaseNotifyManager databaseNotifyManager) { + this(context, localPublicKeyStorage, lastUpdateInteractor, new OperationLog(), 0, + databaseNotifyManager); } private KeyWritableRepository(Context context, LocalPublicKeyStorage localPublicKeyStorage, - LastUpdateInteractor lastUpdateInteractor, OperationLog log, int indent) { + LastUpdateInteractor lastUpdateInteractor, OperationLog log, int indent, + DatabaseNotifyManager databaseNotifyManager) { super(context.getContentResolver(), localPublicKeyStorage, log, indent); - mContext = context; + this.context = context; + this.databaseNotifyManager = databaseNotifyManager; this.lastUpdateInteractor = lastUpdateInteractor; } private LongSparseArray getTrustedMasterKeys() { - Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] { + 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 @@ -530,7 +536,7 @@ 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 = mContentResolver.delete( + int deleted = contentResolver.delete( KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); if (deleted > 0) { log(LogType.MSG_IP_DELETE_OLD_OK); @@ -540,7 +546,8 @@ public class KeyWritableRepository extends KeyRepository { } log(LogType.MSG_IP_APPLY_BATCH); - mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + contentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); + databaseNotifyManager.notifyKeyChange(masterKeyId); log(LogType.MSG_IP_SUCCESS); return result; @@ -603,7 +610,7 @@ public class KeyWritableRepository extends KeyRepository { // insert new version of this keyRing Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId); - return mContentResolver.insert(uri, values); + return contentResolver.insert(uri, values); } public boolean deleteKeyRing(long masterKeyId) { @@ -613,8 +620,11 @@ public class KeyWritableRepository extends KeyRepository { Timber.e(e, "Could not delete file!"); return false; } - mContentResolver.delete(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId),null, null); - int deletedRows = mContentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); + contentResolver.delete(ApiAutocryptPeer.buildByMasterKeyId(masterKeyId),null, null); + int deletedRows = contentResolver.delete(KeyRingData.buildPublicKeyRingUri(masterKeyId), null, null); + + databaseNotifyManager.notifyKeyChange(masterKeyId); + return deletedRows > 0; } @@ -690,7 +700,7 @@ public class KeyWritableRepository extends KeyRepository { // first, mark all keys as not available ContentValues values = new ContentValues(); values.put(Keys.HAS_SECRET, SecretKeyType.GNU_DUMMY.getNum()); - mContentResolver.update(uri, values, null, null); + contentResolver.update(uri, values, null, null); // then, mark exactly the keys we have available log(LogType.MSG_IS_IMPORTING_SUBKEYS); @@ -699,7 +709,7 @@ public class KeyWritableRepository extends KeyRepository { long id = sub.getKeyId(); SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive(); values.put(Keys.HAS_SECRET, mode.getNum()); - int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", + int upd = contentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[]{Long.toString(id)}); if (upd == 1) { switch (mode) { @@ -1030,11 +1040,11 @@ public class KeyWritableRepository extends KeyRepository { log.add(LogType.MSG_TRUST, 0); Cursor cursor; - Preferences preferences = Preferences.getPreferences(mContext); + Preferences preferences = Preferences.getPreferences(context); boolean isTrustDbInitialized = preferences.isKeySignaturesTableInitialized(); if (!isTrustDbInitialized) { log.add(LogType.MSG_TRUST_INITIALIZE, 1); - cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), + cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] { KeyRings.MASTER_KEY_ID }, null, null, null); } else { String[] signerMasterKeyIdStrings = new String[signerMasterKeyIds.size()]; @@ -1044,7 +1054,7 @@ public class KeyWritableRepository extends KeyRepository { signerMasterKeyIdStrings[i++] = Long.toString(masterKeyId); } - cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsFilterBySigner(), + cursor = contentResolver.query(KeyRings.buildUnifiedKeyRingsFilterBySigner(), new String[] { KeyRings.MASTER_KEY_ID }, null, signerMasterKeyIdStrings, null); } 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 a3d160151..d581de0f5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -50,6 +50,7 @@ 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; +import org.sufficientlysecure.keychain.util.DatabaseUtil; import timber.log.Timber; import static android.database.DatabaseUtils.dumpCursorToString; @@ -101,7 +102,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe String authority = KeychainContract.CONTENT_AUTHORITY; - /** + /* * list key_rings * *
@@ -124,7 +125,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                         + "/" + KeychainContract.PATH_USER_IDS,
                 KEY_RINGS_USER_IDS);
 
-        /**
+        /*
          * find by criteria other than master key id
          *
          * key_rings/find/email/_
@@ -144,7 +145,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                         + KeychainContract.PATH_FILTER + "/" + KeychainContract.PATH_BY_SIGNER,
                 KEY_RINGS_FILTER_BY_SIGNER);
 
-        /**
+        /*
          * list key_ring specifics
          *
          * 
@@ -189,7 +190,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                         + KeychainContract.PATH_CERTS + "/*/*",
                 KEY_RING_CERTS_SPECIFIC);
 
-        /**
+        /*
          * API apps
          *
          * 
@@ -205,7 +206,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
         matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
                 + KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
 
-        /**
+        /*
          * Trust Identity access
          *
          * 
@@ -221,7 +222,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                 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);
@@ -835,7 +836,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
 
         SQLiteDatabase db = getDb().getReadableDatabase();
 
-        Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
+        Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy);
         if (cursor != null) {
             // Tell the cursor what uri to watch, so it knows when its source data changes
             cursor.setNotificationUri(getContext().getContentResolver(), uri);
@@ -849,27 +850,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);
-            Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + rawQuery, selectionArgs);
-
-            // this is a debugging feature, we can be a little careless
-            explainCursor.moveToFirst();
-
-            StringBuilder line = new StringBuilder();
-            for (int i = 0; i < explainCursor.getColumnCount(); i++) {
-                line.append(explainCursor.getColumnName(i)).append(", ");
-            }
-            Timber.d(line.toString());
-
-            while (!explainCursor.isAfterLast()) {
-                line = new StringBuilder();
-                for (int i = 0; i < explainCursor.getColumnCount(); i++) {
-                    line.append(explainCursor.getString(i)).append(", ");
-                }
-                Timber.d(line.toString());
-                explainCursor.moveToNext();
-            }
-
-            explainCursor.close();
+            DatabaseUtil.explainQuery(db, rawQuery);
         }
 
         return cursor;
@@ -966,9 +947,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                 rowUri = uri;
             }
 
-            // notify of changes in db
-            getContext().getContentResolver().notifyChange(uri, null);
-
         } catch (SQLiteConstraintException e) {
             Timber.d(e, "Constraint exception on insert! Entry already existing?");
         }
@@ -1003,7 +981,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                 }
                 // corresponding keys and userIds are deleted by ON DELETE CASCADE
                 count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs);
-                contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1)), null);
                 break;
             }
             case KEY_RING_SECRET: {
@@ -1013,7 +990,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                     selection += " AND (" + additionalSelection + ")";
                 }
                 count = db.delete(Tables.KEY_RINGS_SECRET, selection, selectionArgs);
-                contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1)), null);
                 break;
             }
 
@@ -1024,20 +1000,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                 String selection = ApiAutocryptPeer.PACKAGE_NAME + " = ? AND " + ApiAutocryptPeer.IDENTIFIER + " = ?";
                 selectionArgs = new String[] { packageName, autocryptPeer };
 
-                Cursor cursor = db.query(Tables.API_AUTOCRYPT_PEERS, new String[] { ApiAutocryptPeer.MASTER_KEY_ID },
-                        selection, selectionArgs, null, null, null);
-                Long masterKeyId = null;
-                if (cursor != null && cursor.moveToNext() && !cursor.isNull(0)) {
-                    masterKeyId = cursor.getLong(0);
-                }
-
                 count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
-
-                if (masterKeyId != null) {
-                    contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null);
-                }
-                contentResolver.notifyChange(
-                        ApiAutocryptPeer.buildByPackageNameAndAutocryptId(packageName, autocryptPeer), null);
                 break;
             }
 
@@ -1047,7 +1010,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                     selection += " AND (" + additionalSelection + ")";
                 }
                 count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
-                contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(uri.getLastPathSegment()), null);
                 break;
 
             case API_APPS_BY_PACKAGE_NAME: {
@@ -1076,7 +1038,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
         Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")");
 
         final SQLiteDatabase db = getDb().getWritableDatabase();
-        ContentResolver contentResolver = getContext().getContentResolver();
 
         int count = 0;
         try {
@@ -1127,25 +1088,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
                         db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values);
                     }
 
-                    Long masterKeyId = values.getAsLong(ApiAutocryptPeer.MASTER_KEY_ID);
-                    if (masterKeyId != null) {
-                        contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(masterKeyId), null);
-                    }
-                    Long gossipMasterKeyId = values.getAsLong(ApiAutocryptPeer.GOSSIP_MASTER_KEY_ID);
-                    if (gossipMasterKeyId != null && !gossipMasterKeyId.equals(masterKeyId)) {
-                        contentResolver.notifyChange(KeyRings.buildGenericKeyRingUri(gossipMasterKeyId), null);
-                    }
-
                     break;
                 }
                 default: {
                     throw new UnsupportedOperationException("Unknown uri: " + uri);
                 }
             }
-
-            // notify of changes in db
-            contentResolver.notifyChange(uri, null);
-
         } catch (SQLiteConstraintException e) {
             Timber.d(e, "Constraint exception on update! Entry already existing?");
         }
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 83f58ae2c..89520cb1a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LastUpdateInteractor.java
@@ -15,14 +15,16 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
 
 public class LastUpdateInteractor {
     private final ContentResolver contentResolver;
+    private final DatabaseNotifyManager databaseNotifyManager;
 
 
     public static LastUpdateInteractor create(Context context) {
-        return new LastUpdateInteractor(context.getContentResolver());
+        return new LastUpdateInteractor(context.getContentResolver(), DatabaseNotifyManager.create(context));
     }
 
-    private LastUpdateInteractor(ContentResolver contentResolver) {
+    private LastUpdateInteractor(ContentResolver contentResolver, DatabaseNotifyManager databaseNotifyManager) {
         this.contentResolver = contentResolver;
+        this.databaseNotifyManager = databaseNotifyManager;
     }
 
     @Nullable
@@ -69,6 +71,8 @@ public class LastUpdateInteractor {
 
         // this will actually update/replace, doing the right thing™ for seenOnKeyservers value
         // see `KeychainProvider.insert()`
-        return contentResolver.insert(UpdatedKeys.CONTENT_URI, values);
+        Uri insert = contentResolver.insert(UpdatedKeys.CONTENT_URI, values);
+        databaseNotifyManager.notifyKeyserverStatusChange(masterKeyId);
+        return insert;
     }
 }
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 328c1ca8d..2253e3e4d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java
@@ -25,6 +25,7 @@ import java.util.List;
 
 import android.content.ContentProvider;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
@@ -40,6 +41,7 @@ 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.ApiApps;
 import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
 import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
@@ -72,6 +74,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
     private UriMatcher uriMatcher;
     private ApiPermissionHelper apiPermissionHelper;
     private KeychainProvider internalKeychainProvider;
+    private DatabaseNotifyManager databaseNotifyManager;
 
 
     /**
@@ -103,9 +106,15 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
     public boolean onCreate() {
         uriMatcher = buildUriMatcher();
 
+        Context context = getContext();
+        if (context == null) {
+            throw new NullPointerException("Context can't be null during onCreate!");
+        }
+
         internalKeychainProvider = new KeychainProvider();
-        internalKeychainProvider.attachInfo(getContext(), null);
-        apiPermissionHelper = new ApiPermissionHelper(getContext(), new ApiDataAccessObject(internalKeychainProvider));
+        internalKeychainProvider.attachInfo(context, null);
+        apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider));
+        databaseNotifyManager = DatabaseNotifyManager.create(context);
         return true;
     }
 
@@ -270,7 +279,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
                 }
                 if (!isWildcardSelector && queriesAutocryptResult) {
                     AutocryptPeerDataAccessObject autocryptPeerDao =
-                            new AutocryptPeerDataAccessObject(internalKeychainProvider, callingPackageName);
+                            new AutocryptPeerDataAccessObject(internalKeychainProvider, callingPackageName,
+                                    databaseNotifyManager);
                     fillTempTableWithAutocryptRecommendations(db, autocryptPeerDao, selectionArgs);
                 }
 
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java
similarity index 100%
rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java
rename to OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusDao.java
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java
index 2f9e79ce8..dd21accd6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java
@@ -17,8 +17,13 @@
 
 package org.sufficientlysecure.keychain.util;
 
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
 import android.text.TextUtils;
 
+import timber.log.Timber;
+
+
 /**
  * Shamelessly copied from android.database.DatabaseUtils
  */
@@ -50,4 +55,28 @@ public class DatabaseUtil {
         System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
         return result;
     }
+
+    public static void explainQuery(SQLiteDatabase db, String sql) {
+        Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + sql, new String[0]);
+
+        // this is a debugging feature, we can be a little careless
+        explainCursor.moveToFirst();
+
+        StringBuilder line = new StringBuilder();
+        for (int i = 0; i < explainCursor.getColumnCount(); i++) {
+            line.append(explainCursor.getColumnName(i)).append(", ");
+        }
+        Timber.d(line.toString());
+
+        while (!explainCursor.isAfterLast()) {
+            line = new StringBuilder();
+            for (int i = 0; i < explainCursor.getColumnCount(); i++) {
+                line.append(explainCursor.getString(i)).append(", ");
+            }
+            Timber.d(line.toString());
+            explainCursor.moveToNext();
+        }
+
+        explainCursor.close();
+    }
 }
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtils.java
deleted file mode 100644
index 69e4f05fe..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtils.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.sufficientlysecure.keychain.util;
-
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
-import timber.log.Timber;
-
-
-public class DatabaseUtils {
-    public static void explainQuery(SQLiteDatabase db, String sql) {
-        Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + sql, new String[0]);
-
-        // this is a debugging feature, we can be a little careless
-        explainCursor.moveToFirst();
-
-        StringBuilder line = new StringBuilder();
-        for (int i = 0; i < explainCursor.getColumnCount(); i++) {
-            line.append(explainCursor.getColumnName(i)).append(", ");
-        }
-        Timber.d(line.toString());
-
-        while (!explainCursor.isAfterLast()) {
-            line = new StringBuilder();
-            for (int i = 0; i < explainCursor.getColumnCount(); i++) {
-                line.append(explainCursor.getString(i)).append(", ");
-            }
-            Timber.d(line.toString());
-            explainCursor.moveToNext();
-        }
-
-        explainCursor.close();
-    }
-}
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 457fc2adb..6cdc1428d 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,8 @@ public class InteropTest {
 
         KeyWritableRepository helper = new KeyWritableRepository(RuntimeEnvironment.application,
                 LocalPublicKeyStorage.getInstance(RuntimeEnvironment.application),
-                LastUpdateInteractor.create(RuntimeEnvironment.application)) {
+                LastUpdateInteractor.create(RuntimeEnvironment.application),
+                DatabaseNotifyManager.create(RuntimeEnvironment.application)) {
 
             @Override
             public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {