Merge pull request #2816 from k9mail/recipients-from-crypto-provider
query recipients also from crypto provider, if available
This commit is contained in:
commit
53345b77dc
2 changed files with 206 additions and 14 deletions
|
@ -21,6 +21,7 @@ import com.fsck.k9.R;
|
|||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient;
|
||||
import com.fsck.k9.view.RecipientSelectView.RecipientCryptoStatus;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
||||
|
@ -58,6 +59,13 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
private static final int INDEX_CONTACT_ID_FOR_NICKNAME = 0;
|
||||
private static final int INDEX_NICKNAME = 1;
|
||||
|
||||
private static final String[] PROJECTION_CRYPTO_ADDRESSES = {
|
||||
"address",
|
||||
"uid_address"
|
||||
};
|
||||
|
||||
private static final int INDEX_USER_ID = 1;
|
||||
|
||||
private static final String[] PROJECTION_CRYPTO_STATUS = {
|
||||
"address",
|
||||
"uid_key_status",
|
||||
|
@ -77,6 +85,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
private final Uri contactUri;
|
||||
private final Uri lookupKeyUri;
|
||||
private final String cryptoProvider;
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
private List<Recipient> cachedRecipients;
|
||||
private ForceLoadContentObserver observerContact, observerKey;
|
||||
|
@ -89,6 +98,8 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
this.addresses = null;
|
||||
this.contactUri = null;
|
||||
this.cryptoProvider = cryptoProvider;
|
||||
|
||||
contentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
public RecipientLoader(Context context, String cryptoProvider, Address... addresses) {
|
||||
|
@ -98,6 +109,8 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
this.contactUri = null;
|
||||
this.cryptoProvider = cryptoProvider;
|
||||
this.lookupKeyUri = null;
|
||||
|
||||
contentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
public RecipientLoader(Context context, String cryptoProvider, Uri contactUri, boolean isLookupKey) {
|
||||
|
@ -107,6 +120,8 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
this.contactUri = isLookupKey ? null : contactUri;
|
||||
this.lookupKeyUri = isLookupKey ? contactUri : null;
|
||||
this.cryptoProvider = cryptoProvider;
|
||||
|
||||
contentResolver = context.getContentResolver();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,6 +135,10 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
fillContactDataFromEmailContentUri(contactUri, recipients, recipientMap);
|
||||
} else if (query != null) {
|
||||
fillContactDataFromQuery(query, recipients, recipientMap);
|
||||
|
||||
if (cryptoProvider != null) {
|
||||
fillContactDataFromCryptoProvider(query, recipients, recipientMap);
|
||||
}
|
||||
} else if (lookupKeyUri != null) {
|
||||
fillContactDataFromLookupKey(lookupKeyUri, recipients, recipientMap);
|
||||
} else {
|
||||
|
@ -137,6 +156,40 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
return recipients;
|
||||
}
|
||||
|
||||
private void fillContactDataFromCryptoProvider(String query, List<Recipient> recipients,
|
||||
Map<String, Recipient> recipientMap) {
|
||||
Cursor cursor;
|
||||
try {
|
||||
Uri queryUri = Uri.parse("content://" + cryptoProvider + ".provider.exported/autocrypt_status");
|
||||
cursor = contentResolver.query(queryUri, PROJECTION_CRYPTO_ADDRESSES, null,
|
||||
new String[] { "%" + query + "%" }, null);
|
||||
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Timber.e(e, "Couldn't obtain recipients from crypto provider!");
|
||||
return;
|
||||
}
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
String uid = cursor.getString(INDEX_USER_ID);
|
||||
Address[] addresses = Address.parseUnencoded(uid);
|
||||
|
||||
for (Address address : addresses) {
|
||||
if (recipientMap.containsKey(address.getAddress())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Recipient recipient = new Recipient(address);
|
||||
recipients.add(recipient);
|
||||
recipientMap.put(address.getAddress(), recipient);
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
private void fillContactDataFromAddresses(Address[] addresses, List<Recipient> recipients,
|
||||
Map<String, Recipient> recipientMap) {
|
||||
for (Address address : addresses) {
|
||||
|
@ -149,7 +202,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
|
||||
private void fillContactDataFromEmailContentUri(Uri contactUri, List<Recipient> recipients,
|
||||
Map<String, Recipient> recipientMap) {
|
||||
Cursor cursor = getContext().getContentResolver().query(contactUri, PROJECTION, null, null, null);
|
||||
Cursor cursor = contentResolver.query(contactUri, PROJECTION, null, null, null);
|
||||
|
||||
if (cursor == null) {
|
||||
return;
|
||||
|
@ -161,14 +214,14 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
private void fillContactDataFromLookupKey(Uri lookupKeyUri, List<Recipient> recipients,
|
||||
Map<String, Recipient> recipientMap) {
|
||||
// We could use the contact id from the URI directly, but getting it from the lookup key is safer
|
||||
Uri contactContentUri = Contacts.lookupContact(getContext().getContentResolver(), lookupKeyUri);
|
||||
Uri contactContentUri = Contacts.lookupContact(contentResolver, lookupKeyUri);
|
||||
if (contactContentUri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String contactIdStr = getContactIdFromContactUri(contactContentUri);
|
||||
|
||||
Cursor cursor = getContext().getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
|
||||
PROJECTION, ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=?",
|
||||
new String[] { contactIdStr }, null);
|
||||
|
@ -190,7 +243,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
|
||||
Uri queryUriForNickname = ContactsContract.Data.CONTENT_URI;
|
||||
|
||||
return getContext().getContentResolver().query(queryUriForNickname,
|
||||
return contentResolver.query(queryUriForNickname,
|
||||
PROJECTION_NICKNAME,
|
||||
ContactsContract.CommonDataKinds.Nickname.NAME + " LIKE ? AND " +
|
||||
Data.MIMETYPE + " = ?",
|
||||
|
@ -215,7 +268,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
private void registerContentObserver() {
|
||||
if (observerContact != null) {
|
||||
observerContact = new ForceLoadContentObserver();
|
||||
getContext().getContentResolver().registerContentObserver(Email.CONTENT_URI, false, observerContact);
|
||||
contentResolver.registerContentObserver(Email.CONTENT_URI, false, observerContact);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,8 +278,6 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
|
||||
boolean hasContact = false;
|
||||
|
||||
final ContentResolver contentResolver = getContext().getContentResolver();
|
||||
|
||||
Uri queryUri = Email.CONTENT_URI;
|
||||
|
||||
Cursor nicknameCursor = getNicknameCursor(nickname);
|
||||
|
@ -257,8 +308,6 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
|
||||
private boolean fillContactDataFromNameAndEmail(String query, List<Recipient> recipients,
|
||||
Map<String, Recipient> recipientMap) {
|
||||
|
||||
ContentResolver contentResolver = getContext().getContentResolver();
|
||||
query = "%" + query + "%";
|
||||
|
||||
Uri queryUri = Email.CONTENT_URI;
|
||||
|
@ -345,8 +394,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
Cursor cursor;
|
||||
Uri queryUri = Uri.parse("content://" + cryptoProvider + ".provider.exported/autocrypt_status");
|
||||
try {
|
||||
cursor = getContext().getContentResolver().query(queryUri, PROJECTION_CRYPTO_STATUS, null,
|
||||
recipientAddresses, null);
|
||||
cursor = contentResolver.query(queryUri, PROJECTION_CRYPTO_STATUS, null, recipientAddresses, null);
|
||||
} catch (SecurityException e) {
|
||||
// TODO escalate error to crypto status?
|
||||
return;
|
||||
|
@ -390,7 +438,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
|
||||
if (observerKey != null) {
|
||||
observerKey = new ForceLoadContentObserver();
|
||||
getContext().getContentResolver().registerContentObserver(queryUri, false, observerKey);
|
||||
contentResolver.registerContentObserver(queryUri, false, observerKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,10 +474,10 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
|
|||
super.onAbandon();
|
||||
|
||||
if (observerKey != null) {
|
||||
getContext().getContentResolver().unregisterContentObserver(observerKey);
|
||||
contentResolver.unregisterContentObserver(observerKey);
|
||||
}
|
||||
if (observerContact != null) {
|
||||
getContext().getContentResolver().unregisterContentObserver(observerContact);
|
||||
contentResolver.unregisterContentObserver(observerContact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
package com.fsck.k9.activity.compose;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.fsck.k9.K9RobolectricTestRunner;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient;
|
||||
import com.fsck.k9.view.RecipientSelectView.RecipientCryptoStatus;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.AdditionalMatchers.aryEq;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(K9RobolectricTestRunner.class)
|
||||
public class RecipientLoaderTest {
|
||||
static final String CRYPTO_PROVIDER = "cryptoProvider";
|
||||
static final String[] PROJECTION_CRYPTO_ADDRESSES = { "address", "uid_address" };
|
||||
static final String[] PROJECTION_CRYPTO_STATUS = { "address", "uid_key_status", "autocrypt_key_status" };
|
||||
static final Address CONTACT_ADDRESS_1 = Address.parse("Contact Name <address@example.org>")[0];
|
||||
static final Address CONTACT_ADDRESS_2 = Address.parse("Other Contact Name <address_two@example.org>")[0];
|
||||
static final String QUERYSTRING = "querystring";
|
||||
|
||||
|
||||
Context context;
|
||||
ContentResolver contentResolver;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
context = mock(Context.class);
|
||||
contentResolver = mock(ContentResolver.class);
|
||||
|
||||
when(context.getContentResolver()).thenReturn(contentResolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCryptoProvider() throws Exception {
|
||||
RecipientLoader recipientLoader = new RecipientLoader(context, CRYPTO_PROVIDER, QUERYSTRING);
|
||||
|
||||
setupQueryCryptoProvider("%" + QUERYSTRING + "%", CONTACT_ADDRESS_1, CONTACT_ADDRESS_2);
|
||||
|
||||
List<Recipient> recipients = recipientLoader.loadInBackground();
|
||||
|
||||
assertEquals(2, recipients.size());
|
||||
assertEquals(CONTACT_ADDRESS_1, recipients.get(0).address);
|
||||
assertEquals(CONTACT_ADDRESS_2, recipients.get(1).address);
|
||||
assertEquals(RecipientCryptoStatus.UNAVAILABLE, recipients.get(0).getCryptoStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCryptoStatus_unavailable() throws Exception {
|
||||
RecipientLoader recipientLoader = new RecipientLoader(context, CRYPTO_PROVIDER, CONTACT_ADDRESS_1);
|
||||
|
||||
setupCryptoProviderStatus(CONTACT_ADDRESS_1, "0", "0");
|
||||
|
||||
List<Recipient> recipients = recipientLoader.loadInBackground();
|
||||
|
||||
assertEquals(1, recipients.size());
|
||||
Recipient recipient = recipients.get(0);
|
||||
assertEquals(CONTACT_ADDRESS_1, recipient.address);
|
||||
assertEquals(RecipientCryptoStatus.UNAVAILABLE, recipient.getCryptoStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCryptoStatus_autocrypt_untrusted() throws Exception {
|
||||
RecipientLoader recipientLoader = new RecipientLoader(context, CRYPTO_PROVIDER, CONTACT_ADDRESS_1);
|
||||
|
||||
setupCryptoProviderStatus(CONTACT_ADDRESS_1, "0", "1");
|
||||
|
||||
List<Recipient> recipients = recipientLoader.loadInBackground();
|
||||
|
||||
assertEquals(1, recipients.size());
|
||||
Recipient recipient = recipients.get(0);
|
||||
assertEquals(CONTACT_ADDRESS_1, recipient.address);
|
||||
assertEquals(RecipientCryptoStatus.AVAILABLE_UNTRUSTED, recipient.getCryptoStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCryptoStatus_autocrypt_trusted() throws Exception {
|
||||
RecipientLoader recipientLoader = new RecipientLoader(context, CRYPTO_PROVIDER, CONTACT_ADDRESS_1);
|
||||
|
||||
setupCryptoProviderStatus(CONTACT_ADDRESS_1, "0", "2");
|
||||
|
||||
List<Recipient> recipients = recipientLoader.loadInBackground();
|
||||
|
||||
assertEquals(1, recipients.size());
|
||||
Recipient recipient = recipients.get(0);
|
||||
assertEquals(CONTACT_ADDRESS_1, recipient.address);
|
||||
assertEquals(RecipientCryptoStatus.AVAILABLE_TRUSTED, recipient.getCryptoStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryCryptoStatus_withHigherUidStatus() throws Exception {
|
||||
RecipientLoader recipientLoader = new RecipientLoader(context, CRYPTO_PROVIDER, CONTACT_ADDRESS_1);
|
||||
|
||||
setupCryptoProviderStatus(CONTACT_ADDRESS_1, "2", "1");
|
||||
|
||||
List<Recipient> recipients = recipientLoader.loadInBackground();
|
||||
|
||||
assertEquals(1, recipients.size());
|
||||
Recipient recipient = recipients.get(0);
|
||||
assertEquals(CONTACT_ADDRESS_1, recipient.address);
|
||||
assertEquals(RecipientCryptoStatus.AVAILABLE_TRUSTED, recipient.getCryptoStatus());
|
||||
}
|
||||
|
||||
private void setupQueryCryptoProvider(String queriedAddress, Address... contactAddresses) {
|
||||
MatrixCursor cursor = new MatrixCursor(PROJECTION_CRYPTO_ADDRESSES);
|
||||
for (Address contactAddress : contactAddresses) {
|
||||
cursor.addRow(new String[] { queriedAddress, contactAddress.toString() });
|
||||
}
|
||||
|
||||
when(contentResolver
|
||||
.query(eq(Uri.parse("content://" + CRYPTO_PROVIDER + ".provider.exported/autocrypt_status")),
|
||||
aryEq(PROJECTION_CRYPTO_ADDRESSES), any(String.class),
|
||||
aryEq(new String[] { queriedAddress }),
|
||||
any(String.class))).thenReturn(cursor);
|
||||
}
|
||||
|
||||
private void setupCryptoProviderStatus(Address address, String uidStatus, String autocryptStatus) {
|
||||
MatrixCursor cursorCryptoStatus = new MatrixCursor(PROJECTION_CRYPTO_STATUS);
|
||||
cursorCryptoStatus.addRow(new String[] { address.getAddress(), uidStatus, autocryptStatus });
|
||||
|
||||
when(contentResolver
|
||||
.query(eq(Uri.parse("content://" + CRYPTO_PROVIDER + ".provider.exported/autocrypt_status")),
|
||||
aryEq(PROJECTION_CRYPTO_STATUS), any(String.class),
|
||||
aryEq(new String[] { address.getAddress() }),
|
||||
any(String.class))).thenReturn(cursorCryptoStatus);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue