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