From c741b33bc22fc87eb75dbc448898ebeba88dbe0c Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Wed, 31 May 2017 21:49:40 +0200 Subject: add the 4 most frequently contacted contacts as app shortcuts --- .../java/de/pixart/messenger/parser/IqParser.java | 1 + .../messenger/persistance/DatabaseBackend.java | 18 +++ .../pixart/messenger/services/AvatarService.java | 21 ++++ .../pixart/messenger/services/ShortcutService.java | 131 +++++++++++++++++++++ .../messenger/services/XmppConnectionService.java | 16 ++- .../messenger/ui/StartConversationActivity.java | 11 +- 6 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 src/main/java/de/pixart/messenger/services/ShortcutService.java (limited to 'src/main') diff --git a/src/main/java/de/pixart/messenger/parser/IqParser.java b/src/main/java/de/pixart/messenger/parser/IqParser.java index bfb2fa71e..1783fed07 100644 --- a/src/main/java/de/pixart/messenger/parser/IqParser.java +++ b/src/main/java/de/pixart/messenger/parser/IqParser.java @@ -83,6 +83,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateRosterUi(); + mXmppConnectionService.getShortcutService().refresh(); } public String avatarData(final IqPacket packet) { diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index ddf516f93..7b49eb376 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -48,6 +48,7 @@ import de.pixart.messenger.entities.Message; import de.pixart.messenger.entities.PresenceTemplate; import de.pixart.messenger.entities.Roster; import de.pixart.messenger.entities.ServiceDiscoveryResult; +import de.pixart.messenger.services.ShortcutService; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; import de.pixart.messenger.xmpp.mam.MamReference; @@ -1432,4 +1433,21 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("delete from " + START_TIMES_TABLE); } } + + public List getFrequentContacts(int days) { + SQLiteDatabase db = this.getReadableDatabase(); + final String SQL = "select " + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + " from " + Conversation.TABLENAME + " join " + Message.TABLENAME + " on conversations.uuid=messages.conversationUuid where messages.status!=0 and carbon==0 and conversations.mode=0 and messages.timeSent>=? group by conversations.uuid order by count(body) desc limit 4;"; + String[] whereArgs = new String[]{String.valueOf(System.currentTimeMillis() - (Config.MILLISECONDS_IN_DAY * days))}; + Cursor cursor = db.rawQuery(SQL, whereArgs); + ArrayList contacts = new ArrayList<>(); + while (cursor.moveToNext()) { + try { + contacts.add(new ShortcutService.FrequentContact(cursor.getString(0), Jid.fromString(cursor.getString(1)))); + } catch (Exception e) { + Log.d(Config.LOGTAG, e.getMessage()); + } + } + cursor.close(); + return contacts; + } } diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java index 15c49940f..3d3af8d26 100644 --- a/src/main/java/de/pixart/messenger/services/AvatarService.java +++ b/src/main/java/de/pixart/messenger/services/AvatarService.java @@ -3,9 +3,12 @@ package de.pixart.messenger.services; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Typeface; import android.net.Uri; +import android.util.DisplayMetrics; import android.util.Log; import java.util.ArrayList; @@ -69,6 +72,24 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { return avatar; } + public Bitmap getRoundedShortcut(final Contact contact) { + DisplayMetrics metrics = mXmppConnectionService.getResources().getDisplayMetrics(); + int size = Math.round(metrics.density * 48); + Bitmap bitmap = get(contact, size); + Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getWidth() / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + return output; + } + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { Contact c = user.getContact(); if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null || user.getAvatar() == null)) { diff --git a/src/main/java/de/pixart/messenger/services/ShortcutService.java b/src/main/java/de/pixart/messenger/services/ShortcutService.java new file mode 100644 index 000000000..56345c663 --- /dev/null +++ b/src/main/java/de/pixart/messenger/services/ShortcutService.java @@ -0,0 +1,131 @@ +package de.pixart.messenger.services; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Build; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import de.pixart.messenger.Config; +import de.pixart.messenger.entities.Account; +import de.pixart.messenger.entities.Contact; +import de.pixart.messenger.ui.StartConversationActivity; +import de.pixart.messenger.utils.ReplacingSerialSingleThreadExecutor; +import de.pixart.messenger.xmpp.jid.Jid; + +public class ShortcutService { + private final XmppConnectionService xmppConnectionService; + private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(false); + + public ShortcutService(XmppConnectionService xmppConnectionService) { + this.xmppConnectionService = xmppConnectionService; + } + + public void refresh() { + refresh(false); + } + + public void refresh(final boolean forceUpdate) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + final Runnable r = new Runnable() { + @Override + public void run() { + refreshImpl(forceUpdate); + } + }; + replacingSerialSingleThreadExecutor.execute(r); + } + } + + @TargetApi(25) + public void report(Contact contact) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class); + shortcutManager.reportShortcutUsed(getShortcutId(contact)); + } + } + + @TargetApi(25) + private void refreshImpl(boolean forceUpdate) { + List frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30); + HashMap accounts = new HashMap<>(); + for (Account account : xmppConnectionService.getAccounts()) { + accounts.put(account.getUuid(), account); + } + List contacts = new ArrayList<>(); + for (FrequentContact frequentContact : frequentContacts) { + Account account = accounts.get(frequentContact.account); + if (account != null) { + contacts.add(account.getRoster().getContact(frequentContact.contact)); + } + } + ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class); + boolean needsUpdate = forceUpdate || contactsChanged(contacts, shortcutManager.getDynamicShortcuts()); + if (!needsUpdate) { + Log.d(Config.LOGTAG, "skipping shortcut update"); + return; + } + List newDynamicShortCuts = new ArrayList<>(); + for (Contact contact : contacts) { + ShortcutInfo shortcut = new ShortcutInfo.Builder(xmppConnectionService, getShortcutId(contact)) + .setShortLabel(contact.getDisplayName()) + .setIntent(getShortcutIntent(contact)) + .setIcon(Icon.createWithBitmap(xmppConnectionService.getAvatarService().getRoundedShortcut(contact))) + .build(); + newDynamicShortCuts.add(shortcut); + } + if (shortcutManager.setDynamicShortcuts(newDynamicShortCuts)) { + Log.d(Config.LOGTAG, "updated dynamic shortcuts"); + } else { + Log.d(Config.LOGTAG, "unable to update dynamic shortcuts"); + } + } + + private static boolean contactsChanged(List needles, List haystack) { + for (Contact needle : needles) { + if (!contactExists(needle, haystack)) { + return true; + } + } + return needles.size() != haystack.size(); + } + + @TargetApi(25) + private static boolean contactExists(Contact needle, List haystack) { + for (ShortcutInfo shortcutInfo : haystack) { + if (getShortcutId(needle).equals(shortcutInfo.getId()) && needle.getDisplayName().equals(shortcutInfo.getShortLabel())) { + return true; + } + } + return false; + } + + private static String getShortcutId(Contact contact) { + return contact.getAccount().getJid().toBareJid().toPreppedString() + "#" + contact.getJid().toBareJid().toPreppedString(); + } + + private Intent getShortcutIntent(Contact contact) { + Intent intent = new Intent(xmppConnectionService, StartConversationActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse("xmpp:" + contact.getJid().toBareJid().toString())); + intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString()); + return intent; + } + + public static class FrequentContact { + private final String account; + private final Jid contact; + + public FrequentContact(String account, Jid contact) { + this.account = account; + this.contact = contact; + } + } +} diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 61a9d2318..27412e1ea 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -70,6 +70,7 @@ import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import de.duenndns.ssl.MemorizingTrustManager; @@ -189,8 +190,9 @@ public class XmppConnectionService extends Service { }; private FileBackend fileBackend = new FileBackend(this); private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService = new NotificationService( - this); + private NotificationService mNotificationService = new NotificationService(this); + private ShortcutService mShortcutService = new ShortcutService(this); + private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false); private OnMessagePacketReceived mMessageParser = new MessageParser(this); private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); private IqParser mIqParser = new IqParser(this); @@ -1663,6 +1665,7 @@ public class XmppConnectionService extends Service { } } Log.d(Config.LOGTAG, "finished merging phone contacts"); + mShortcutService.refresh(mInitialAddressbookSyncCompleted.compareAndSet(false, true)); updateAccountUi(); } }); @@ -3788,10 +3791,11 @@ public class XmppConnectionService extends Service { return this.mMessageArchiveService; } - public List findContacts(Jid jid) { + public List findContacts(Jid jid, String accountJid) { ArrayList contacts = new ArrayList<>(); for (Account account : getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { + if (!account.isOptionSet(Account.OPTION_DISABLED) + && (accountJid == null || accountJid.equals(account.getJid().toBareJid().toString()))) { Contact contact = account.getRoster().getContactFromRoster(jid); if (contact != null) { contacts.add(contact); @@ -4176,6 +4180,10 @@ public class XmppConnectionService extends Service { } } + public ShortcutService getShortcutService() { + return mShortcutService; + } + public interface OnMamPreferencesFetched { void onPreferencesFetched(Element prefs); diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index f3cbf7be9..73400dde3 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -831,7 +831,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU case Intent.ACTION_VIEW: Uri uri = intent.getData(); if (uri != null) { - return new Invite(intent.getData(), false).invite(); + Invite invite = new Invite(intent.getData(), false); + invite.account = intent.getStringExtra("account"); + return invite.invite(); } else { return false; } @@ -870,7 +872,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU finish(); return true; } - List contacts = xmppConnectionService.findContacts(invite.getJid()); + List contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account); if (invite.isMuc()) { Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid()); if (muc != null) { @@ -893,6 +895,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show(); } } + if (invite.account != null) { + xmppConnectionService.getShortcutService().report(contact); + } switchToConversation(contact, invite.getBody()); } return true; @@ -1182,6 +1187,8 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU super(uri, safeSource); } + public String account; + boolean invite() { if (getJid() != null) { return handleJid(this); -- cgit v1.2.3