support contact shortcuts (#2918)

Ye feng created

* support contact shortcuts

* make ShortcutActivity extends AbstractSearchableListItemActivity

* Draw the app icon in the corner of the icon and modify the name of the widget

* updated label and icon size

Change summary

src/main/AndroidManifest.xml                                       | 10 
src/main/java/eu/siacs/conversations/services/AvatarService.java   | 36 
src/main/java/eu/siacs/conversations/services/ShortcutService.java | 41 
src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java      | 68 
src/main/res/values/strings.xml                                    |  2 
5 files changed, 148 insertions(+), 9 deletions(-)

Detailed changes

src/main/AndroidManifest.xml 🔗

@@ -106,6 +106,9 @@
             android:name=".ui.StartConversationActivity"
             android:label="@string/title_activity_start_conversation"
             android:launchMode="singleTop">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+            </intent-filter>
         </activity>
         <activity
             android:name=".ui.WelcomeActivity"
@@ -222,7 +225,12 @@
             android:exported="false"
             android:grantUriPermissions="true"/>
 
-
+        <activity android:name=".ui.ShortcutActivity"
+            android:label="@string/contact">
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+            </intent-filter>
+        </activity>
     </application>
 
 </manifest>

src/main/java/eu/siacs/conversations/services/AvatarService.java 🔗

@@ -1,6 +1,8 @@
 package eu.siacs.conversations.services;
 
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
@@ -21,6 +23,7 @@ import java.util.Locale;
 import java.util.Set;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
@@ -76,21 +79,48 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 	}
 
 	public Bitmap getRoundedShortcut(final Contact contact) {
+		return getRoundedShortcut(contact,false);
+	}
+
+	public Bitmap getRoundedShortcutWithIcon(final Contact contact){
+		return getRoundedShortcut(contact,true);
+	}
+
+	private Bitmap getRoundedShortcut(final Contact contact,boolean withIcon) {
 		DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics();
 		int size = Math.round(metrics.density * 48);
 		Bitmap bitmap = get(contact,size);
 		Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
 		Canvas canvas = new Canvas(output);
-
 		final Paint paint = new Paint();
-		final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
 
+		drawAvatar(bitmap, canvas, paint);
+		if(withIcon){
+			drawIcon(canvas, paint);
+		}
+		return output;
+	}
+
+	private void drawAvatar(Bitmap bitmap, Canvas canvas, Paint paint) {
+		final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
 		paint.setAntiAlias(true);
 		canvas.drawARGB(0, 0, 0, 0);
 		canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint);
 		paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
 		canvas.drawBitmap(bitmap, rect, rect, paint);
-		return output;
+	}
+
+	private void drawIcon(Canvas canvas, Paint paint) {
+		BitmapFactory.Options opts = new BitmapFactory.Options();
+		opts.inSampleSize = 3;
+		Resources resources = mXmppConnectionService.getResources();
+		Bitmap icon = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher, opts);
+		paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+
+		int left = canvas.getWidth() - icon.getWidth();
+		int top = canvas.getHeight() - icon.getHeight();
+		final Rect rect = new Rect(left, top, left + icon.getWidth(), top + icon.getHeight());
+		canvas.drawBitmap(icon, null, rect, paint);
 	}
 
 	public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) {

src/main/java/eu/siacs/conversations/services/ShortcutService.java 🔗

@@ -4,9 +4,11 @@ import android.annotation.TargetApi;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.NonNull;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -75,11 +77,7 @@ public class ShortcutService {
         }
         List<ShortcutInfo> newDynamicShortCuts = new ArrayList<>();
         for (Contact contact : contacts) {
-            ShortcutInfo shortcut = new ShortcutInfo.Builder(xmppConnectionService, getShortcutId(contact))
-                    .setShortLabel(contact.getDisplayName())
-                    .setIntent(getShortcutIntent(contact))
-                    .setIcon(Icon.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(contact)))
-                    .build();
+            ShortcutInfo shortcut = getShortcutInfo(contact);
             newDynamicShortCuts.add(shortcut);
         }
         if (shortcutManager.setDynamicShortcuts(newDynamicShortCuts)) {
@@ -89,6 +87,15 @@ public class ShortcutService {
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.N_MR1)
+    private ShortcutInfo getShortcutInfo(Contact contact) {
+        return new ShortcutInfo.Builder(xmppConnectionService, getShortcutId(contact))
+                        .setShortLabel(contact.getDisplayName())
+                        .setIntent(getShortcutIntent(contact))
+                        .setIcon(Icon.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(contact)))
+                        .build();
+    }
+
     private static boolean contactsChanged(List<Contact> needles, List<ShortcutInfo> haystack) {
         for(Contact needle : needles) {
             if(!contactExists(needle,haystack)) {
@@ -120,6 +127,30 @@ public class ShortcutService {
         return intent;
     }
 
+    @NonNull
+    public Intent createShortcut(Contact contact) {
+        Intent intent;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            ShortcutInfo shortcut = getShortcutInfo(contact);
+            ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
+            intent = shortcutManager.createShortcutResultIntent(shortcut);
+        } else {
+            intent = createShortcutResultIntent(contact);
+        }
+        return intent;
+    }
+
+    @NonNull
+    private Intent createShortcutResultIntent(Contact contact) {
+        Intent intent;AvatarService avatarService = xmppConnectionService.getAvatarService();
+        Bitmap icon = avatarService.getRoundedShortcutWithIcon(contact);
+        intent = new Intent();
+        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, contact.getDisplayName());
+        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
+        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, getShortcutIntent(contact));
+        return intent;
+    }
+
     public static class FrequentContact {
         private final String account;
         private final Jid contact;

src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java 🔗

@@ -0,0 +1,68 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.Collections;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.ListItem;
+
+public class ShortcutActivity extends AbstractSearchableListItemActivity{
+
+    @Override
+    protected void refreshUiReal() {
+
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getListView().setOnItemClickListener((parent, view, position, id) -> {
+            final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+            imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
+
+            ListItem listItem = getListItems().get(position);
+            Intent shortcut = xmppConnectionService.getShortcutService().createShortcut(((Contact) listItem));
+            setResult(RESULT_OK,shortcut);
+            finish();
+        });
+        binding.fab.setVisibility(View.GONE);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        ActionBar bar = getSupportActionBar();
+        if(bar != null){
+            bar.setTitle(R.string.create_shortcut);
+        }
+    }
+
+    @Override
+    protected void filterContacts(String needle) {
+        getListItems().clear();
+        if (xmppConnectionService == null) {
+            getListItemAdapter().notifyDataSetChanged();
+            return;
+        }
+        for (final Account account : xmppConnectionService.getAccounts()) {
+            if (account.getStatus() != Account.State.DISABLED) {
+                for (final Contact contact : account.getRoster().getContacts()) {
+                    if (contact.showInRoster()
+                            && contact.match(this, needle)) {
+                        getListItems().add(contact);
+                    }
+                }
+            }
+        }
+        Collections.sort(getListItems());
+        getListItemAdapter().notifyDataSetChanged();
+    }
+}

src/main/res/values/strings.xml 🔗

@@ -49,6 +49,7 @@
 	<string name="start_conversation">Start conversation</string>
 	<string name="invite_contact">Invite contact</string>
 	<string name="contacts">Contacts</string>
+	<string name="contact">Contact</string>
 	<string name="cancel">Cancel</string>
 	<string name="set">Set</string>
 	<string name="add">Add</string>
@@ -739,6 +740,7 @@
 	<string name="pref_omemo_setting_summary_always">OMEMO will always be used for one-on-one and private group chats.</string>
 	<string name="pref_omemo_setting_summary_default_on">OMEMO will be used by default for new conversations.</string>
 	<string name="pref_omemo_setting_summary_default_off">OMEMO will have to be turned on explicitly for new conversations.</string>
+	<string name="create_shortcut">Create Shortcut</string>
 	<string name="pref_font_size">Font Size</string>
 	<string name="pref_font_size_summary">The relative font size used within the app.</string>
 	<string name="default_on">On by default</string>