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}