Loading k9mail/src/main/AndroidManifest.xml +13 −0 Original line number Diff line number Diff line Loading @@ -250,6 +250,10 @@ <action android:name="org.autocrypt.PEER_ACTION"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <meta-data android:name="android.service.chooser.chooser_target_service" android:value="com.fsck.k9.service.K9ChooserTargetService" /> </activity> <!-- Search Activity - searchable --> Loading Loading @@ -416,6 +420,15 @@ android:name=".service.DatabaseUpgradeService" android:exported="false"/> <service android:name="com.fsck.k9.service.K9ChooserTargetService" android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE" > <intent-filter> <action android:name="android.service.chooser.ChooserTargetService" /> </intent-filter> </service> <provider android:name=".provider.AttachmentProvider" android:authorities="${applicationId}.attachmentprovider" Loading k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientLoader.java +49 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts.Data; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.fsck.k9.R; Loading Loading @@ -112,6 +113,16 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { private List<Recipient> cachedRecipients; private ForceLoadContentObserver observerContact, observerKey; private RecipientLoader(Context context) { super(context); this.query = null; this.lookupKeyUri = null; this.addresses = null; this.contactUri = null; this.cryptoProvider = null; this.contentResolver = context.getContentResolver(); } public RecipientLoader(Context context, String cryptoProvider, String query) { super(context); Loading Loading @@ -146,6 +157,15 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { contentResolver = context.getContentResolver(); } public static RecipientLoader getMostContactedRecipientLoader(Context context, final int maxRecipients) { return new RecipientLoader(context) { @Override public List<Recipient> loadInBackground() { return super.fillContactDataBySortOrder(maxRecipients); } }; } @Override public List<Recipient> loadInBackground() { List<Recipient> recipients = new ArrayList<>(); Loading @@ -167,6 +187,11 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { throw new IllegalStateException("loader must be initialized with query or list of addresses!"); } return fillCryptoStatusData(recipients, recipientMap); } @NonNull private List<Recipient> fillCryptoStatusData(List<Recipient> recipients, Map<String, Recipient> recipientMap) { if (recipients.isEmpty()) { return recipients; } Loading @@ -178,6 +203,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { return recipients; } private void fillContactDataFromCryptoProvider(String query, List<Recipient> recipients, Map<String, Recipient> recipientMap) { Cursor cursor; Loading Loading @@ -279,7 +305,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { boolean foundValidCursor = false; foundValidCursor |= fillContactDataFromNickname(query, recipients, recipientMap); foundValidCursor |= fillContactDataFromNameAndEmail(query, recipients, recipientMap); foundValidCursor |= fillContactDataFromNameAndEmail(query, recipients, recipientMap, null); if (foundValidCursor) { Collections.sort(recipients, RECIPIENT_COMPARATOR); Loading Loading @@ -317,7 +343,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { .query(queryUri, PROJECTION, selection, new String[] { id }, SORT_ORDER); String contactNickname = nicknameCursor.getString(INDEX_NICKNAME); fillContactDataFromCursor(cursor, recipients, recipientMap, contactNickname); fillContactDataFromCursor(cursor, recipients, recipientMap, contactNickname, null); hasContact = true; } Loading @@ -328,9 +354,25 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { return hasContact; } private List<Recipient> fillContactDataBySortOrder(int maxRecipients) { List<Recipient> recipients = new ArrayList<>(); Uri queryUri = Email.CONTENT_URI; Cursor cursor = contentResolver.query(queryUri, PROJECTION, null, null, SORT_ORDER); if (cursor == null) { return recipients; } fillContactDataFromCursor(cursor, recipients, new HashMap<String, Recipient>(), null, maxRecipients); return recipients; } private boolean fillContactDataFromNameAndEmail(String query, List<Recipient> recipients, Map<String, Recipient> recipientMap) { Map<String, Recipient> recipientMap, Integer maxTargets) { query = "%" + query + "%"; Uri queryUri = Email.CONTENT_URI; Loading @@ -352,13 +394,13 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { private void fillContactDataFromCursor(Cursor cursor, List<Recipient> recipients, Map<String, Recipient> recipientMap) { fillContactDataFromCursor(cursor, recipients, recipientMap, null); fillContactDataFromCursor(cursor, recipients, recipientMap, null, null); } private void fillContactDataFromCursor(Cursor cursor, List<Recipient> recipients, Map<String, Recipient> recipientMap, @Nullable String prefilledName) { Map<String, Recipient> recipientMap, @Nullable String prefilledName, @Nullable Integer maxRecipients) { while (cursor.moveToNext()) { while (cursor.moveToNext() && (maxRecipients == null || recipients.size() < maxRecipients)) { String name = prefilledName != null ? prefilledName : cursor.getString(INDEX_NAME); String email = cursor.getString(INDEX_EMAIL); Loading Loading @@ -508,4 +550,5 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { contentResolver.unregisterContentObserver(observerContact); } } } k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java +50 −5 Original line number Diff line number Diff line Loading @@ -9,12 +9,15 @@ import java.util.regex.Pattern; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.net.Uri; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.widget.ImageView; Loading @@ -32,6 +35,7 @@ import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.load.resource.file.FileToStreamDecoder; import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder; import com.bumptech.glide.request.FutureTarget; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.fsck.k9.helper.Contacts; Loading @@ -56,7 +60,7 @@ public class ContactPictureLoader { private static final String FALLBACK_CONTACT_LETTER = "?"; private Resources mResources; private final Context context; private Contacts mContactsHelper; private int mPictureSizeInPx; Loading Loading @@ -102,11 +106,11 @@ public class ContactPictureLoader { * use a dynamically calculated background color. */ public ContactPictureLoader(Context context, int defaultBackgroundColor) { Context appContext = context.getApplicationContext(); mResources = appContext.getResources(); mContactsHelper = Contacts.getInstance(appContext); this.context = context.getApplicationContext(); mContactsHelper = Contacts.getInstance(this.context); float scale = mResources.getDisplayMetrics().density; Resources resources = context.getResources(); float scale = resources.getDisplayMetrics().density; mPictureSizeInPx = (int) (PICTURE_SIZE * scale); mDefaultBackgroundColor = defaultBackgroundColor; Loading Loading @@ -172,6 +176,37 @@ public class ContactPictureLoader { } } public Bitmap loadContactPictureIcon(Recipient recipient) { return loadContactPicture(recipient.photoThumbnailUri, recipient.address); } @WorkerThread private Bitmap loadContactPicture(Uri photoUri, Address address) { FutureTarget<Bitmap> bitmapTarget; if (photoUri != null) { bitmapTarget = Glide.with(context) .load(photoUri) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.NONE) .dontAnimate() .into(mPictureSizeInPx, mPictureSizeInPx); } else { bitmapTarget = Glide.with(context) .using(new FallbackGlideModelLoader(), FallbackGlideParams.class) .from(FallbackGlideParams.class) .as(Bitmap.class) .decoder(new FallbackGlideBitmapDecoder(context)) .encoder(new BitmapEncoder(CompressFormat.PNG, 0)) .cacheDecoder(new FileToStreamDecoder<>(new StreamBitmapDecoder(context))) .diskCacheStrategy(DiskCacheStrategy.NONE) .load(new FallbackGlideParams(address)) .dontAnimate() .into(mPictureSizeInPx, mPictureSizeInPx); } return loadIgnoringErors(bitmapTarget); } private int calcUnknownContactColor(Address address) { if (mDefaultBackgroundColor != 0) { return mDefaultBackgroundColor; Loading Loading @@ -271,4 +306,14 @@ public class ContactPictureLoader { } } @WorkerThread @Nullable private <T> T loadIgnoringErors(FutureTarget<T> target) { try { return target.get(); } catch (Exception e) { return null; } } } k9mail/src/main/java/com/fsck/k9/service/K9ChooserTargetService.java 0 → 100644 +93 −0 Original line number Diff line number Diff line package com.fsck.k9.service; import java.util.ArrayList; import java.util.List; import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.os.Build; import android.os.Bundle; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.fsck.k9.activity.MessageCompose; import com.fsck.k9.activity.compose.RecipientLoader; import com.fsck.k9.activity.misc.ContactPictureLoader; import com.fsck.k9.helper.ContactPicture; import com.fsck.k9.mail.Address; import com.fsck.k9.view.RecipientSelectView.Recipient; @TargetApi(Build.VERSION_CODES.M) public class K9ChooserTargetService extends ChooserTargetService { private static final int MAX_TARGETS = 5; private RecipientLoader recipientLoader; private ContactPictureLoader contactPictureLoader; @Override public void onCreate() { super.onCreate(); Context applicationContext = getApplicationContext(); recipientLoader = RecipientLoader.getMostContactedRecipientLoader(applicationContext, MAX_TARGETS); contactPictureLoader = ContactPicture.getContactPictureLoader(applicationContext); } @Override public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { List<Recipient> recipients = recipientLoader.loadInBackground(); return createChooserTargets(recipients); } @NonNull private List<ChooserTarget> createChooserTargets(List<Recipient> recipients) { float score = 1.0f; List<ChooserTarget> targets = new ArrayList<>(); ComponentName componentName = new ComponentName(this, MessageCompose.class); for (Recipient recipient : recipients) { Bundle intentExtras = prepareIntentExtras(recipient); Icon icon = loadRecipientIcon(recipient); ChooserTarget chooserTarget = new ChooserTarget(recipient.getDisplayNameOrAddress(), icon, score, componentName, intentExtras); targets.add(chooserTarget); score -= 0.1; } return targets; } @NonNull private Bundle prepareIntentExtras(Recipient recipient) { Address address = recipient.address; Bundle extras = new Bundle(); extras.putStringArray(Intent.EXTRA_EMAIL, new String[] { address.toString() }); extras.putStringArray(Intent.EXTRA_CC, new String[0]); extras.putStringArray(Intent.EXTRA_BCC, new String[0]); return extras; } @Nullable private Icon loadRecipientIcon(Recipient recipient) { Bitmap bitmap = contactPictureLoader.loadContactPictureIcon(recipient); if (bitmap == null) { return null; } return Icon.createWithBitmap(bitmap); } } k9mail/src/test/java/com/fsck/k9/activity/compose/RecipientLoaderTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading Loading @@ -55,6 +56,8 @@ public class RecipientLoaderTest { static final String TYPE = "" + TYPE_HOME; static final String[] CONTACT_1 = new String[] { "0", "Bob", "bob", "bob@host.com", TYPE, null, "1", null, "100", "Bob" }; static final String[] CONTACT_2 = new String[] { "2", "Bob2", "bob2", "bob2@host.com", TYPE, null, "2", null, "99", "Bob2" }; static final String[] CONTACT_NO_EMAIL = new String[] { "0", "Bob", "bob", null, TYPE, null, "1", null, "10", "Bob_noMail" }; static final String[] CONTACT_WITH_NICKNAME_NOT_CONTACTED = Loading Loading @@ -247,4 +250,59 @@ public class RecipientLoaderTest { assertEquals("bob@host.com", recipients.get(0).address.getAddress()); assertEquals("eve_notContacted@host.com", recipients.get(1).address.getAddress()); } @Test public void getMostContactedFoundMore() throws Exception { int maxTargets = 1; setupContactProvider(CONTACT_1, CONTACT_2); RecipientLoader recipientLoader = RecipientLoader.getMostContactedRecipientLoader(context, maxTargets); List<Recipient> recipients = recipientLoader.loadInBackground(); assertEquals(maxTargets, recipients.size()); assertEquals("bob@host.com", recipients.get(0).address.getAddress()); assertEquals(RecipientCryptoStatus.UNDEFINED, recipients.get(0).getCryptoStatus()); } @Test public void getMostContactedFoundLess() throws Exception { int maxTargets = 5; setupContactProvider(CONTACT_1, CONTACT_2); RecipientLoader recipientLoader = RecipientLoader.getMostContactedRecipientLoader(context, maxTargets); List<Recipient> recipients = recipientLoader.loadInBackground(); assertEquals(2, recipients.size()); assertEquals("bob@host.com", recipients.get(0).address.getAddress()); assertEquals(RecipientCryptoStatus.UNDEFINED, recipients.get(0).getCryptoStatus()); assertEquals("bob2@host.com", recipients.get(1).address.getAddress()); assertEquals(RecipientCryptoStatus.UNDEFINED, recipients.get(1).getCryptoStatus()); } @Test public void getMostContactedFoundNothing() throws Exception { int maxTargets = 5; setupContactProvider(); RecipientLoader recipientLoader = RecipientLoader.getMostContactedRecipientLoader(context, maxTargets); List<Recipient> recipients = recipientLoader.loadInBackground(); assertEquals(0, recipients.size()); } private void setupContactProvider(String[]... contacts) { MatrixCursor cursor = new MatrixCursor(PROJECTION); for (String[] contact : contacts) { cursor.addRow(contact); } when(contentResolver .query(eq(Email.CONTENT_URI), aryEq(PROJECTION), isNull(String.class), isNull(String[].class), any(String.class))).thenReturn(cursor); } } Loading
k9mail/src/main/AndroidManifest.xml +13 −0 Original line number Diff line number Diff line Loading @@ -250,6 +250,10 @@ <action android:name="org.autocrypt.PEER_ACTION"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <meta-data android:name="android.service.chooser.chooser_target_service" android:value="com.fsck.k9.service.K9ChooserTargetService" /> </activity> <!-- Search Activity - searchable --> Loading Loading @@ -416,6 +420,15 @@ android:name=".service.DatabaseUpgradeService" android:exported="false"/> <service android:name="com.fsck.k9.service.K9ChooserTargetService" android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE" > <intent-filter> <action android:name="android.service.chooser.ChooserTargetService" /> </intent-filter> </service> <provider android:name=".provider.AttachmentProvider" android:authorities="${applicationId}.attachmentprovider" Loading
k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientLoader.java +49 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts.Data; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.fsck.k9.R; Loading Loading @@ -112,6 +113,16 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { private List<Recipient> cachedRecipients; private ForceLoadContentObserver observerContact, observerKey; private RecipientLoader(Context context) { super(context); this.query = null; this.lookupKeyUri = null; this.addresses = null; this.contactUri = null; this.cryptoProvider = null; this.contentResolver = context.getContentResolver(); } public RecipientLoader(Context context, String cryptoProvider, String query) { super(context); Loading Loading @@ -146,6 +157,15 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { contentResolver = context.getContentResolver(); } public static RecipientLoader getMostContactedRecipientLoader(Context context, final int maxRecipients) { return new RecipientLoader(context) { @Override public List<Recipient> loadInBackground() { return super.fillContactDataBySortOrder(maxRecipients); } }; } @Override public List<Recipient> loadInBackground() { List<Recipient> recipients = new ArrayList<>(); Loading @@ -167,6 +187,11 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { throw new IllegalStateException("loader must be initialized with query or list of addresses!"); } return fillCryptoStatusData(recipients, recipientMap); } @NonNull private List<Recipient> fillCryptoStatusData(List<Recipient> recipients, Map<String, Recipient> recipientMap) { if (recipients.isEmpty()) { return recipients; } Loading @@ -178,6 +203,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { return recipients; } private void fillContactDataFromCryptoProvider(String query, List<Recipient> recipients, Map<String, Recipient> recipientMap) { Cursor cursor; Loading Loading @@ -279,7 +305,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { boolean foundValidCursor = false; foundValidCursor |= fillContactDataFromNickname(query, recipients, recipientMap); foundValidCursor |= fillContactDataFromNameAndEmail(query, recipients, recipientMap); foundValidCursor |= fillContactDataFromNameAndEmail(query, recipients, recipientMap, null); if (foundValidCursor) { Collections.sort(recipients, RECIPIENT_COMPARATOR); Loading Loading @@ -317,7 +343,7 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { .query(queryUri, PROJECTION, selection, new String[] { id }, SORT_ORDER); String contactNickname = nicknameCursor.getString(INDEX_NICKNAME); fillContactDataFromCursor(cursor, recipients, recipientMap, contactNickname); fillContactDataFromCursor(cursor, recipients, recipientMap, contactNickname, null); hasContact = true; } Loading @@ -328,9 +354,25 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { return hasContact; } private List<Recipient> fillContactDataBySortOrder(int maxRecipients) { List<Recipient> recipients = new ArrayList<>(); Uri queryUri = Email.CONTENT_URI; Cursor cursor = contentResolver.query(queryUri, PROJECTION, null, null, SORT_ORDER); if (cursor == null) { return recipients; } fillContactDataFromCursor(cursor, recipients, new HashMap<String, Recipient>(), null, maxRecipients); return recipients; } private boolean fillContactDataFromNameAndEmail(String query, List<Recipient> recipients, Map<String, Recipient> recipientMap) { Map<String, Recipient> recipientMap, Integer maxTargets) { query = "%" + query + "%"; Uri queryUri = Email.CONTENT_URI; Loading @@ -352,13 +394,13 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { private void fillContactDataFromCursor(Cursor cursor, List<Recipient> recipients, Map<String, Recipient> recipientMap) { fillContactDataFromCursor(cursor, recipients, recipientMap, null); fillContactDataFromCursor(cursor, recipients, recipientMap, null, null); } private void fillContactDataFromCursor(Cursor cursor, List<Recipient> recipients, Map<String, Recipient> recipientMap, @Nullable String prefilledName) { Map<String, Recipient> recipientMap, @Nullable String prefilledName, @Nullable Integer maxRecipients) { while (cursor.moveToNext()) { while (cursor.moveToNext() && (maxRecipients == null || recipients.size() < maxRecipients)) { String name = prefilledName != null ? prefilledName : cursor.getString(INDEX_NAME); String email = cursor.getString(INDEX_EMAIL); Loading Loading @@ -508,4 +550,5 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> { contentResolver.unregisterContentObserver(observerContact); } } }
k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java +50 −5 Original line number Diff line number Diff line Loading @@ -9,12 +9,15 @@ import java.util.regex.Pattern; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.net.Uri; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.text.TextUtils; import android.widget.ImageView; Loading @@ -32,6 +35,7 @@ import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.load.resource.file.FileToStreamDecoder; import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder; import com.bumptech.glide.request.FutureTarget; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.fsck.k9.helper.Contacts; Loading @@ -56,7 +60,7 @@ public class ContactPictureLoader { private static final String FALLBACK_CONTACT_LETTER = "?"; private Resources mResources; private final Context context; private Contacts mContactsHelper; private int mPictureSizeInPx; Loading Loading @@ -102,11 +106,11 @@ public class ContactPictureLoader { * use a dynamically calculated background color. */ public ContactPictureLoader(Context context, int defaultBackgroundColor) { Context appContext = context.getApplicationContext(); mResources = appContext.getResources(); mContactsHelper = Contacts.getInstance(appContext); this.context = context.getApplicationContext(); mContactsHelper = Contacts.getInstance(this.context); float scale = mResources.getDisplayMetrics().density; Resources resources = context.getResources(); float scale = resources.getDisplayMetrics().density; mPictureSizeInPx = (int) (PICTURE_SIZE * scale); mDefaultBackgroundColor = defaultBackgroundColor; Loading Loading @@ -172,6 +176,37 @@ public class ContactPictureLoader { } } public Bitmap loadContactPictureIcon(Recipient recipient) { return loadContactPicture(recipient.photoThumbnailUri, recipient.address); } @WorkerThread private Bitmap loadContactPicture(Uri photoUri, Address address) { FutureTarget<Bitmap> bitmapTarget; if (photoUri != null) { bitmapTarget = Glide.with(context) .load(photoUri) .asBitmap() .diskCacheStrategy(DiskCacheStrategy.NONE) .dontAnimate() .into(mPictureSizeInPx, mPictureSizeInPx); } else { bitmapTarget = Glide.with(context) .using(new FallbackGlideModelLoader(), FallbackGlideParams.class) .from(FallbackGlideParams.class) .as(Bitmap.class) .decoder(new FallbackGlideBitmapDecoder(context)) .encoder(new BitmapEncoder(CompressFormat.PNG, 0)) .cacheDecoder(new FileToStreamDecoder<>(new StreamBitmapDecoder(context))) .diskCacheStrategy(DiskCacheStrategy.NONE) .load(new FallbackGlideParams(address)) .dontAnimate() .into(mPictureSizeInPx, mPictureSizeInPx); } return loadIgnoringErors(bitmapTarget); } private int calcUnknownContactColor(Address address) { if (mDefaultBackgroundColor != 0) { return mDefaultBackgroundColor; Loading Loading @@ -271,4 +306,14 @@ public class ContactPictureLoader { } } @WorkerThread @Nullable private <T> T loadIgnoringErors(FutureTarget<T> target) { try { return target.get(); } catch (Exception e) { return null; } } }
k9mail/src/main/java/com/fsck/k9/service/K9ChooserTargetService.java 0 → 100644 +93 −0 Original line number Diff line number Diff line package com.fsck.k9.service; import java.util.ArrayList; import java.util.List; import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.os.Build; import android.os.Bundle; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.fsck.k9.activity.MessageCompose; import com.fsck.k9.activity.compose.RecipientLoader; import com.fsck.k9.activity.misc.ContactPictureLoader; import com.fsck.k9.helper.ContactPicture; import com.fsck.k9.mail.Address; import com.fsck.k9.view.RecipientSelectView.Recipient; @TargetApi(Build.VERSION_CODES.M) public class K9ChooserTargetService extends ChooserTargetService { private static final int MAX_TARGETS = 5; private RecipientLoader recipientLoader; private ContactPictureLoader contactPictureLoader; @Override public void onCreate() { super.onCreate(); Context applicationContext = getApplicationContext(); recipientLoader = RecipientLoader.getMostContactedRecipientLoader(applicationContext, MAX_TARGETS); contactPictureLoader = ContactPicture.getContactPictureLoader(applicationContext); } @Override public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { List<Recipient> recipients = recipientLoader.loadInBackground(); return createChooserTargets(recipients); } @NonNull private List<ChooserTarget> createChooserTargets(List<Recipient> recipients) { float score = 1.0f; List<ChooserTarget> targets = new ArrayList<>(); ComponentName componentName = new ComponentName(this, MessageCompose.class); for (Recipient recipient : recipients) { Bundle intentExtras = prepareIntentExtras(recipient); Icon icon = loadRecipientIcon(recipient); ChooserTarget chooserTarget = new ChooserTarget(recipient.getDisplayNameOrAddress(), icon, score, componentName, intentExtras); targets.add(chooserTarget); score -= 0.1; } return targets; } @NonNull private Bundle prepareIntentExtras(Recipient recipient) { Address address = recipient.address; Bundle extras = new Bundle(); extras.putStringArray(Intent.EXTRA_EMAIL, new String[] { address.toString() }); extras.putStringArray(Intent.EXTRA_CC, new String[0]); extras.putStringArray(Intent.EXTRA_BCC, new String[0]); return extras; } @Nullable private Icon loadRecipientIcon(Recipient recipient) { Bitmap bitmap = contactPictureLoader.loadContactPictureIcon(recipient); if (bitmap == null) { return null; } return Icon.createWithBitmap(bitmap); } }
k9mail/src/test/java/com/fsck/k9/activity/compose/RecipientLoaderTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading Loading @@ -55,6 +56,8 @@ public class RecipientLoaderTest { static final String TYPE = "" + TYPE_HOME; static final String[] CONTACT_1 = new String[] { "0", "Bob", "bob", "bob@host.com", TYPE, null, "1", null, "100", "Bob" }; static final String[] CONTACT_2 = new String[] { "2", "Bob2", "bob2", "bob2@host.com", TYPE, null, "2", null, "99", "Bob2" }; static final String[] CONTACT_NO_EMAIL = new String[] { "0", "Bob", "bob", null, TYPE, null, "1", null, "10", "Bob_noMail" }; static final String[] CONTACT_WITH_NICKNAME_NOT_CONTACTED = Loading Loading @@ -247,4 +250,59 @@ public class RecipientLoaderTest { assertEquals("bob@host.com", recipients.get(0).address.getAddress()); assertEquals("eve_notContacted@host.com", recipients.get(1).address.getAddress()); } @Test public void getMostContactedFoundMore() throws Exception { int maxTargets = 1; setupContactProvider(CONTACT_1, CONTACT_2); RecipientLoader recipientLoader = RecipientLoader.getMostContactedRecipientLoader(context, maxTargets); List<Recipient> recipients = recipientLoader.loadInBackground(); assertEquals(maxTargets, recipients.size()); assertEquals("bob@host.com", recipients.get(0).address.getAddress()); assertEquals(RecipientCryptoStatus.UNDEFINED, recipients.get(0).getCryptoStatus()); } @Test public void getMostContactedFoundLess() throws Exception { int maxTargets = 5; setupContactProvider(CONTACT_1, CONTACT_2); RecipientLoader recipientLoader = RecipientLoader.getMostContactedRecipientLoader(context, maxTargets); List<Recipient> recipients = recipientLoader.loadInBackground(); assertEquals(2, recipients.size()); assertEquals("bob@host.com", recipients.get(0).address.getAddress()); assertEquals(RecipientCryptoStatus.UNDEFINED, recipients.get(0).getCryptoStatus()); assertEquals("bob2@host.com", recipients.get(1).address.getAddress()); assertEquals(RecipientCryptoStatus.UNDEFINED, recipients.get(1).getCryptoStatus()); } @Test public void getMostContactedFoundNothing() throws Exception { int maxTargets = 5; setupContactProvider(); RecipientLoader recipientLoader = RecipientLoader.getMostContactedRecipientLoader(context, maxTargets); List<Recipient> recipients = recipientLoader.loadInBackground(); assertEquals(0, recipients.size()); } private void setupContactProvider(String[]... contacts) { MatrixCursor cursor = new MatrixCursor(PROJECTION); for (String[] contact : contacts) { cursor.addRow(contact); } when(contentResolver .query(eq(Email.CONTENT_URI), aryEq(PROJECTION), isNull(String.class), isNull(String[].class), any(String.class))).thenReturn(cursor); } }