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