Merge pull request #3244 from k9mail/mynimu-directShare_GH2550

Add Direct Share support
This commit is contained in:
cketti 2018-03-16 05:53:43 +01:00 committed by GitHub
commit 119144d256
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 263 additions and 11 deletions

View file

@ -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 -->
@ -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"

View file

@ -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;
@ -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);
@ -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<>();
@ -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;
}
@ -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;
@ -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);
@ -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;
}
@ -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;
@ -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);
@ -508,4 +550,5 @@ public class RecipientLoader extends AsyncTaskLoader<List<Recipient>> {
contentResolver.unregisterContentObserver(observerContact);
}
}
}

View file

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

View file

@ -0,0 +1,93 @@
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);
}
}

View file

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