Merge pull request #2816 from k9mail/recipients-from-crypto-provider

query recipients also from crypto provider, if available
This commit is contained in:
Vincent Breitmoser 2017-10-05 15:40:36 +02:00 committed by GitHub
commit 53345b77dc
2 changed files with 206 additions and 14 deletions

View file

@ -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);
}
}
}

View file

@ -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);
}
}