Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 119144d2 authored by cketti's avatar cketti Committed by GitHub
Browse files

Merge pull request #3244 from k9mail/mynimu-directShare_GH2550

Add Direct Share support
parents 4cb007b2 402f0adb
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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"
+49 −6
Original line number Diff line number Diff line
@@ -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);
        }
    }

}
+50 −5
Original line number Diff line number Diff line
@@ -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;
        }
    }

}
+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);
    }
}
+58 −0
Original line number Diff line number Diff line
@@ -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);

    }
}