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