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.core.content.pm.ShortcutInfoCompat;
15import androidx.core.graphics.drawable.IconCompat;
16
17import java.util.ArrayList;
18import java.util.HashMap;
19import java.util.List;
20
21import eu.siacs.conversations.Config;
22import eu.siacs.conversations.entities.Account;
23import eu.siacs.conversations.entities.Contact;
24import eu.siacs.conversations.ui.StartConversationActivity;
25import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
26import eu.siacs.conversations.xmpp.Jid;
27
28public class ShortcutService {
29
30 private final XmppConnectionService xmppConnectionService;
31 private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName());
32
33 public ShortcutService(XmppConnectionService xmppConnectionService) {
34 this.xmppConnectionService = xmppConnectionService;
35 }
36
37 public void refresh() {
38 refresh(false);
39 }
40
41 public void refresh(final boolean forceUpdate) {
42 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
43 final Runnable r = new Runnable() {
44 @Override
45 public void run() {
46 refreshImpl(forceUpdate);
47 }
48 };
49 replacingSerialSingleThreadExecutor.execute(r);
50 }
51 }
52
53 @TargetApi(25)
54 public void report(Contact contact) {
55 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
56 ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
57 shortcutManager.reportShortcutUsed(getShortcutId(contact));
58 }
59 }
60
61 @TargetApi(25)
62 private void refreshImpl(boolean forceUpdate) {
63 List<FrequentContact> frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30);
64 HashMap<String,Account> accounts = new HashMap<>();
65 for(Account account : xmppConnectionService.getAccounts()) {
66 accounts.put(account.getUuid(),account);
67 }
68 List<Contact> contacts = new ArrayList<>();
69 for(FrequentContact frequentContact : frequentContacts) {
70 Account account = accounts.get(frequentContact.account);
71 if (account != null) {
72 contacts.add(account.getRoster().getContact(frequentContact.contact));
73 }
74 }
75 ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
76 boolean needsUpdate = forceUpdate || contactsChanged(contacts,shortcutManager.getDynamicShortcuts());
77 if (!needsUpdate) {
78 Log.d(Config.LOGTAG,"skipping shortcut update");
79 return;
80 }
81 List<ShortcutInfo> newDynamicShortCuts = new ArrayList<>();
82 for (Contact contact : contacts) {
83 ShortcutInfo shortcut = getShortcutInfo(contact);
84 newDynamicShortCuts.add(shortcut);
85 }
86 if (shortcutManager.setDynamicShortcuts(newDynamicShortCuts)) {
87 Log.d(Config.LOGTAG,"updated dynamic shortcuts");
88 } else {
89 Log.d(Config.LOGTAG, "unable to update dynamic shortcuts");
90 }
91 }
92
93 public ShortcutInfoCompat getShortcutInfoCompat(Contact contact) {
94 return new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(contact))
95 .setShortLabel(contact.getDisplayName())
96 .setIntent(getShortcutIntent(contact))
97 .setIcon(IconCompat.createFromIcon(Icon.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(contact))))
98 .setIsConversation()
99 .build();
100 }
101
102 @TargetApi(Build.VERSION_CODES.N_MR1)
103 private ShortcutInfo getShortcutInfo(Contact contact) {
104 return getShortcutInfoCompat(contact).toShortcutInfo();
105 }
106
107 private static boolean contactsChanged(List<Contact> needles, List<ShortcutInfo> haystack) {
108 for(Contact needle : needles) {
109 if(!contactExists(needle,haystack)) {
110 return true;
111 }
112 }
113 return needles.size() != haystack.size();
114 }
115
116 @TargetApi(25)
117 private static boolean contactExists(Contact needle, List<ShortcutInfo> haystack) {
118 for(ShortcutInfo shortcutInfo : haystack) {
119 if (getShortcutId(needle).equals(shortcutInfo.getId()) && needle.getDisplayName().equals(shortcutInfo.getShortLabel())) {
120 return true;
121 }
122 }
123 return false;
124 }
125
126 private static String getShortcutId(Contact contact) {
127 return contact.getAccount().getJid().asBareJid().toEscapedString()+"#"+contact.getJid().asBareJid().toEscapedString();
128 }
129
130 private Intent getShortcutIntent(Contact contact) {
131 Intent intent = new Intent(xmppConnectionService, StartConversationActivity.class);
132 intent.setAction(Intent.ACTION_VIEW);
133 intent.setData(Uri.parse("xmpp:"+contact.getJid().asBareJid().toEscapedString()));
134 intent.putExtra("account",contact.getAccount().getJid().asBareJid().toString());
135 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP| Intent.FLAG_ACTIVITY_SINGLE_TOP);
136 return intent;
137 }
138
139 @NonNull
140 public Intent createShortcut(Contact contact, boolean legacy) {
141 Intent intent;
142 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !legacy) {
143 ShortcutInfo shortcut = getShortcutInfo(contact);
144 ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class);
145 intent = shortcutManager.createShortcutResultIntent(shortcut);
146 } else {
147 intent = createShortcutResultIntent(contact);
148 }
149 return intent;
150 }
151
152 @NonNull
153 private Intent createShortcutResultIntent(Contact contact) {
154 AvatarService avatarService = xmppConnectionService.getAvatarService();
155 Bitmap icon = avatarService.getRoundedShortcutWithIcon(contact);
156 Intent intent = new Intent();
157 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, contact.getDisplayName());
158 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
159 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, getShortcutIntent(contact));
160 return intent;
161 }
162
163 public static class FrequentContact {
164 private final String account;
165 private final Jid contact;
166
167 public FrequentContact(String account, Jid contact) {
168 this.account = account;
169 this.contact = contact;
170 }
171 }
172
173}