ShortcutService.java

  1package eu.siacs.conversations.services;
  2
  3import android.annotation.TargetApi;
  4import android.content.Intent;
  5import android.content.pm.ShortcutInfo;
  6import android.content.pm.ShortcutManager;
  7import android.graphics.Bitmap;
  8import android.graphics.drawable.Icon;
  9import android.net.Uri;
 10import android.os.Build;
 11import android.util.Log;
 12
 13import androidx.annotation.NonNull;
 14import androidx.annotation.RequiresApi;
 15import androidx.core.content.pm.ShortcutInfoCompat;
 16import androidx.core.graphics.drawable.IconCompat;
 17
 18import java.util.ArrayList;
 19import java.util.HashMap;
 20import java.util.List;
 21import java.util.Set;
 22
 23import eu.siacs.conversations.Config;
 24import eu.siacs.conversations.entities.Account;
 25import eu.siacs.conversations.entities.Contact;
 26import eu.siacs.conversations.entities.MucOptions;
 27import eu.siacs.conversations.ui.StartConversationActivity;
 28import eu.siacs.conversations.ui.ConversationsActivity;
 29import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
 30import eu.siacs.conversations.xmpp.Jid;
 31
 32public class ShortcutService {
 33
 34    private final XmppConnectionService xmppConnectionService;
 35    private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName());
 36
 37    public ShortcutService(XmppConnectionService xmppConnectionService) {
 38        this.xmppConnectionService = xmppConnectionService;
 39    }
 40
 41    public void refresh() {
 42        refresh(false);
 43    }
 44
 45    public void refresh(final boolean forceUpdate) {
 46        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
 47            final Runnable r = new Runnable() {
 48                @Override
 49                public void run() {
 50                    refreshImpl(forceUpdate);
 51                }
 52            };
 53            replacingSerialSingleThreadExecutor.execute(r);
 54        }
 55    }
 56
 57    @TargetApi(25)
 58    public void report(Contact contact) {
 59        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
 60            ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
 61            shortcutManager.reportShortcutUsed(getShortcutId(contact));
 62        }
 63    }
 64
 65    @TargetApi(25)
 66    private void refreshImpl(boolean forceUpdate) {
 67        List<FrequentContact> frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30);
 68        HashMap<String,Account> accounts = new HashMap<>();
 69        for(Account account : xmppConnectionService.getAccounts()) {
 70            accounts.put(account.getUuid(),account);
 71        }
 72        List<Contact> contacts = new ArrayList<>();
 73        for(FrequentContact frequentContact : frequentContacts) {
 74            Account account = accounts.get(frequentContact.account);
 75            if (account != null) {
 76                contacts.add(account.getRoster().getContact(frequentContact.contact));
 77            }
 78        }
 79        ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
 80        boolean needsUpdate = forceUpdate || contactsChanged(contacts,shortcutManager.getDynamicShortcuts());
 81        if (!needsUpdate) {
 82            Log.d(Config.LOGTAG,"skipping shortcut update");
 83            return;
 84        }
 85        List<ShortcutInfo> newDynamicShortCuts = new ArrayList<>();
 86        for (Contact contact : contacts) {
 87            ShortcutInfo shortcut = getShortcutInfo(contact);
 88            newDynamicShortCuts.add(shortcut);
 89        }
 90        if (shortcutManager.setDynamicShortcuts(newDynamicShortCuts)) {
 91            Log.d(Config.LOGTAG,"updated dynamic shortcuts");
 92        } else {
 93            Log.d(Config.LOGTAG, "unable to update dynamic shortcuts");
 94        }
 95    }
 96
 97    public ShortcutInfoCompat getShortcutInfoCompat(Contact contact) {
 98        return new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(contact))
 99                        .setShortLabel(contact.getDisplayName())
100                        .setIntent(getShortcutIntent(contact))
101                        .setIcon(IconCompat.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(contact)))
102                        .setIsConversation()
103                        .setCategories(Set.of("com.cheogram.android.SHARE_TARGET"))
104                        .build();
105    }
106
107    public ShortcutInfoCompat getShortcutInfoCompat(final MucOptions mucOptions) {
108        return new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(mucOptions))
109                        .setShortLabel(mucOptions.getConversation().getName())
110                        .setIntent(getShortcutIntent(mucOptions))
111                        .setIcon(IconCompat.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(mucOptions)))
112                        .setIsConversation()
113                        .setCategories(Set.of("com.cheogram.android.SHARE_TARGET"))
114                        .build();
115    }
116
117    @TargetApi(Build.VERSION_CODES.N_MR1)
118    private ShortcutInfo getShortcutInfo(final Contact contact) {
119        return getShortcutInfoCompat(contact).toShortcutInfo();
120    }
121
122    private static boolean contactsChanged(List<Contact> needles, List<ShortcutInfo> haystack) {
123        for(Contact needle : needles) {
124            if(!contactExists(needle,haystack)) {
125                return true;
126            }
127        }
128        return needles.size() != haystack.size();
129    }
130
131    @TargetApi(25)
132    private static boolean contactExists(Contact needle, List<ShortcutInfo> haystack) {
133        for(ShortcutInfo shortcutInfo : haystack) {
134            if (getShortcutId(needle).equals(shortcutInfo.getId()) && needle.getDisplayName().equals(shortcutInfo.getShortLabel())) {
135                return true;
136            }
137        }
138        return false;
139    }
140
141    private static String getShortcutId(Contact contact) {
142        return contact.getAccount().getJid().asBareJid().toEscapedString()+"#"+contact.getJid().asBareJid().toEscapedString();
143    }
144
145    private static String getShortcutId(final MucOptions mucOptions) {
146        final Account account = mucOptions.getAccount();
147        final Jid jid = mucOptions.getConversation().getJid();
148        return account.getJid().asBareJid().toEscapedString()
149                + "#"
150                + jid.asBareJid().toEscapedString();
151    }
152
153    private Intent getShortcutIntent(final MucOptions mucOptions) {
154        final Account account = mucOptions.getAccount();
155        return getShortcutIntent(
156                account,
157                Uri.parse(
158                        String.format(
159                                "xmpp:%s?join",
160                                mucOptions
161                                        .getConversation()
162                                        .getJid()
163                                        .asBareJid()
164                                        .toEscapedString())));
165    }
166
167    private Intent getShortcutIntent(final Contact contact) {
168        return getShortcutIntent(
169                contact.getAccount(),
170                Uri.parse("xmpp:" + contact.getJid().asBareJid().toEscapedString()));
171    }
172
173    private Intent getShortcutIntent(final Account account, final Uri uri) {
174        Intent intent = new Intent(xmppConnectionService, StartConversationActivity.class);
175        intent.setAction(Intent.ACTION_VIEW);
176        intent.setData(uri);
177        intent.putExtra("account", account.getJid().asBareJid().toString());
178        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
179        return intent;
180    }
181
182    @NonNull
183    public Intent createShortcut(Contact contact, boolean legacy) {
184        Intent intent;
185        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !legacy) {
186            ShortcutInfo shortcut = getShortcutInfo(contact);
187            ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
188            intent = shortcutManager.createShortcutResultIntent(shortcut);
189        } else {
190            intent = createShortcutResultIntent(contact);
191        }
192        return intent;
193    }
194
195    @NonNull
196    private Intent createShortcutResultIntent(Contact contact) {
197        AvatarService avatarService = xmppConnectionService.getAvatarService();
198        Bitmap icon = avatarService.getRoundedShortcutWithIcon(contact);
199        Intent intent = new Intent();
200        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, contact.getDisplayName());
201        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
202        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, getShortcutIntent(contact));
203        return intent;
204    }
205
206    public static class FrequentContact {
207        private final String account;
208        private final Jid contact;
209
210        public FrequentContact(String account, Jid contact) {
211            this.account = account;
212            this.contact = contact;
213        }
214    }
215
216}