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}