refactored phone contact loading in preperation for sync
This commit is contained in:
parent
4df0cc3657
commit
a49a5790c7
|
@ -15,4 +15,8 @@ public class QuickConversationsService {
|
||||||
public static boolean isFull() {
|
public static boolean isFull() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void considerSync() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package eu.siacs.conversations.android;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
abstract class AbstractPhoneContact {
|
||||||
|
|
||||||
|
private final Uri lookupUri;
|
||||||
|
private final String displayName;
|
||||||
|
private final String photoUri;
|
||||||
|
|
||||||
|
|
||||||
|
AbstractPhoneContact(Cursor cursor) {
|
||||||
|
int phoneId = cursor.getInt(cursor.getColumnIndex(ContactsContract.Data._ID));
|
||||||
|
String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY));
|
||||||
|
this.lookupUri = ContactsContract.Contacts.getLookupUri(phoneId, lookupKey);
|
||||||
|
this.displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
|
||||||
|
this.photoUri = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.PHOTO_URI));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getLookupUri() {
|
||||||
|
return lookupUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhotoUri() {
|
||||||
|
return photoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int rating() {
|
||||||
|
return (TextUtils.isEmpty(displayName) ? 0 : 2) + (TextUtils.isEmpty(photoUri) ? 0 : 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package eu.siacs.conversations.android;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import rocks.xmpp.addr.Jid;
|
||||||
|
|
||||||
|
public class JabberIdContact extends AbstractPhoneContact {
|
||||||
|
|
||||||
|
private final Jid jid;
|
||||||
|
|
||||||
|
private JabberIdContact(Cursor cursor) throws IllegalArgumentException {
|
||||||
|
super(cursor);
|
||||||
|
try {
|
||||||
|
this.jid = Jid.of(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
|
||||||
|
} catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jid getJid() {
|
||||||
|
return jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(Context context, OnPhoneContactsLoaded<JabberIdContact> callback) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
callback.onPhoneContactsLoaded(Collections.emptyList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
|
||||||
|
ContactsContract.Data.DISPLAY_NAME,
|
||||||
|
ContactsContract.Data.PHOTO_URI,
|
||||||
|
ContactsContract.Data.LOOKUP_KEY,
|
||||||
|
ContactsContract.CommonDataKinds.Im.DATA};
|
||||||
|
|
||||||
|
final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
|
||||||
|
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
|
||||||
|
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
|
||||||
|
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
|
||||||
|
+ "\")";
|
||||||
|
final Cursor cursor;
|
||||||
|
try {
|
||||||
|
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.onPhoneContactsLoaded(Collections.emptyList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final HashMap<Jid, JabberIdContact> contacts = new HashMap<>();
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
final JabberIdContact contact = new JabberIdContact(cursor);
|
||||||
|
final JabberIdContact preexisting = contacts.put(contact.getJid(), contact);
|
||||||
|
if (preexisting == null || preexisting.rating() < contact.rating()) {
|
||||||
|
contacts.put(contact.getJid(), contact);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(Config.LOGTAG,"unable to create jabber id contact");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
callback.onPhoneContactsLoaded(contacts.values());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package eu.siacs.conversations.android;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface OnPhoneContactsLoaded<T extends AbstractPhoneContact> {
|
||||||
|
|
||||||
|
void onPhoneContactsLoaded(Collection<T> contacts);
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
private String commonName;
|
private String commonName;
|
||||||
protected Jid jid;
|
protected Jid jid;
|
||||||
private int subscription = 0;
|
private int subscription = 0;
|
||||||
private String systemAccount;
|
private Uri systemAccount;
|
||||||
private String photoUri;
|
private String photoUri;
|
||||||
private final JSONObject keys;
|
private final JSONObject keys;
|
||||||
private JSONArray groups = new JSONArray();
|
private JSONArray groups = new JSONArray();
|
||||||
|
@ -62,7 +62,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
|
|
||||||
public Contact(final String account, final String systemName, final String serverName,
|
public Contact(final String account, final String systemName, final String serverName,
|
||||||
final Jid jid, final int subscription, final String photoUri,
|
final Jid jid, final int subscription, final String photoUri,
|
||||||
final String systemAccount, final String keys, final String avatar, final long lastseen,
|
final Uri systemAccount, final String keys, final String avatar, final long lastseen,
|
||||||
final String presence, final String groups) {
|
final String presence, final String groups) {
|
||||||
this.accountUuid = account;
|
this.accountUuid = account;
|
||||||
this.systemName = systemName;
|
this.systemName = systemName;
|
||||||
|
@ -105,13 +105,19 @@ public class Contact implements ListItem, Blockable {
|
||||||
// TODO: Borked DB... handle this somehow?
|
// TODO: Borked DB... handle this somehow?
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Uri systemAccount;
|
||||||
|
try {
|
||||||
|
systemAccount = Uri.parse(cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)));
|
||||||
|
} catch (Exception e) {
|
||||||
|
systemAccount = null;
|
||||||
|
}
|
||||||
return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
|
return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
|
||||||
cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
|
cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
|
||||||
cursor.getString(cursor.getColumnIndex(SERVERNAME)),
|
cursor.getString(cursor.getColumnIndex(SERVERNAME)),
|
||||||
jid,
|
jid,
|
||||||
cursor.getInt(cursor.getColumnIndex(OPTIONS)),
|
cursor.getInt(cursor.getColumnIndex(OPTIONS)),
|
||||||
cursor.getString(cursor.getColumnIndex(PHOTOURI)),
|
cursor.getString(cursor.getColumnIndex(PHOTOURI)),
|
||||||
cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
|
systemAccount,
|
||||||
cursor.getString(cursor.getColumnIndex(KEYS)),
|
cursor.getString(cursor.getColumnIndex(KEYS)),
|
||||||
cursor.getString(cursor.getColumnIndex(AVATAR)),
|
cursor.getString(cursor.getColumnIndex(AVATAR)),
|
||||||
cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
|
cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
|
||||||
|
@ -200,7 +206,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
values.put(SERVERNAME, serverName);
|
values.put(SERVERNAME, serverName);
|
||||||
values.put(JID, jid.toString());
|
values.put(JID, jid.toString());
|
||||||
values.put(OPTIONS, subscription);
|
values.put(OPTIONS, subscription);
|
||||||
values.put(SYSTEMACCOUNT, systemAccount);
|
values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null);
|
||||||
values.put(PHOTOURI, photoUri);
|
values.put(PHOTOURI, photoUri);
|
||||||
values.put(KEYS, keys.toString());
|
values.put(KEYS, keys.toString());
|
||||||
values.put(AVATAR, avatar == null ? null : avatar.getFilename());
|
values.put(AVATAR, avatar == null ? null : avatar.getFilename());
|
||||||
|
@ -270,21 +276,11 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getSystemAccount() {
|
public Uri getSystemAccount() {
|
||||||
if (systemAccount == null) {
|
return systemAccount;
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
String[] parts = systemAccount.split("#");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
long id = Long.parseLong(parts[0]);
|
|
||||||
return ContactsContract.Contacts.getLookupUri(id, parts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSystemAccount(String account) {
|
public void setSystemAccount(Uri lookupUri) {
|
||||||
this.systemAccount = account;
|
this.systemAccount = lookupUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<String> getGroups(final boolean unique) {
|
private Collection<String> getGroups(final boolean unique) {
|
||||||
|
@ -343,7 +339,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean showInPhoneBook() {
|
public boolean showInPhoneBook() {
|
||||||
return systemAccount != null && !systemAccount.trim().isEmpty();
|
return systemAccount != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseSubscriptionFromElement(Element item) {
|
public void parseSubscriptionFromElement(Element item) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import rocks.xmpp.addr.Jid;
|
||||||
public class ShortcutService {
|
public class ShortcutService {
|
||||||
|
|
||||||
private final XmppConnectionService xmppConnectionService;
|
private final XmppConnectionService xmppConnectionService;
|
||||||
private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(false);
|
private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName());
|
||||||
|
|
||||||
public ShortcutService(XmppConnectionService xmppConnectionService) {
|
public ShortcutService(XmppConnectionService xmppConnectionService) {
|
||||||
this.xmppConnectionService = xmppConnectionService;
|
this.xmppConnectionService = xmppConnectionService;
|
||||||
|
|
|
@ -71,6 +71,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.android.JabberIdContact;
|
||||||
import eu.siacs.conversations.crypto.OmemoSetting;
|
import eu.siacs.conversations.crypto.OmemoSetting;
|
||||||
import eu.siacs.conversations.crypto.PgpDecryptionService;
|
import eu.siacs.conversations.crypto.PgpDecryptionService;
|
||||||
import eu.siacs.conversations.crypto.PgpEngine;
|
import eu.siacs.conversations.crypto.PgpEngine;
|
||||||
|
@ -115,7 +116,6 @@ import eu.siacs.conversations.utils.ConversationsFileObserver;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
|
|
||||||
import eu.siacs.conversations.utils.PhoneHelper;
|
import eu.siacs.conversations.utils.PhoneHelper;
|
||||||
import eu.siacs.conversations.utils.QuickLoader;
|
import eu.siacs.conversations.utils.QuickLoader;
|
||||||
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
|
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor;
|
||||||
|
@ -192,7 +192,7 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public DatabaseBackend databaseBackend;
|
public DatabaseBackend databaseBackend;
|
||||||
private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true);
|
private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger");
|
||||||
private long mLastActivity = 0;
|
private long mLastActivity = 0;
|
||||||
private FileBackend fileBackend = new FileBackend(this);
|
private FileBackend fileBackend = new FileBackend(this);
|
||||||
private MemorizingTrustManager mMemorizingTrustManager;
|
private MemorizingTrustManager mMemorizingTrustManager;
|
||||||
|
@ -1519,45 +1519,36 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPhoneContacts() {
|
public void loadPhoneContacts() {
|
||||||
mContactMergerExecutor.execute(() -> PhoneHelper.loadPhoneContacts(XmppConnectionService.this, new OnPhoneContactsLoadedListener() {
|
mContactMergerExecutor.execute(() -> {
|
||||||
@Override
|
JabberIdContact.load(this, contacts -> {
|
||||||
public void onPhoneContactsLoaded(List<Bundle> phoneContacts) {
|
Log.d(Config.LOGTAG, "start merging phone contacts with roster");
|
||||||
Log.d(Config.LOGTAG, "start merging phone contacts with roster");
|
for (Account account : accounts) {
|
||||||
for (Account account : accounts) {
|
List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts();
|
||||||
List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts();
|
for (JabberIdContact jidContact : contacts) {
|
||||||
for (Bundle phoneContact : phoneContacts) {
|
final Contact contact = account.getRoster().getContact(jidContact.getJid());
|
||||||
Jid jid;
|
contact.setSystemAccount(jidContact.getLookupUri());
|
||||||
try {
|
boolean needsCacheClean = contact.setPhotoUri(jidContact.getPhotoUri());
|
||||||
jid = Jid.of(phoneContact.getString("jid"));
|
needsCacheClean |= contact.setSystemName(jidContact.getDisplayName());
|
||||||
} catch (final IllegalArgumentException e) {
|
if (needsCacheClean) {
|
||||||
continue;
|
getAvatarService().clear(contact);
|
||||||
}
|
}
|
||||||
final Contact contact = account.getRoster().getContact(jid);
|
withSystemAccounts.remove(contact);
|
||||||
String systemAccount = phoneContact.getInt("phoneid")
|
}
|
||||||
+ "#"
|
for (Contact contact : withSystemAccounts) {
|
||||||
+ phoneContact.getString("lookup");
|
contact.setSystemAccount(null);
|
||||||
contact.setSystemAccount(systemAccount);
|
boolean needsCacheClean = contact.setPhotoUri(null);
|
||||||
boolean needsCacheClean = contact.setPhotoUri(phoneContact.getString("photouri"));
|
needsCacheClean |= contact.setSystemName(null);
|
||||||
needsCacheClean |= contact.setSystemName(phoneContact.getString("displayname"));
|
if (needsCacheClean) {
|
||||||
if (needsCacheClean) {
|
getAvatarService().clear(contact);
|
||||||
getAvatarService().clear(contact);
|
}
|
||||||
}
|
}
|
||||||
withSystemAccounts.remove(contact);
|
}
|
||||||
}
|
Log.d(Config.LOGTAG, "finished merging phone contacts");
|
||||||
for (Contact contact : withSystemAccounts) {
|
mShortcutService.refresh(mInitialAddressbookSyncCompleted.compareAndSet(false, true));
|
||||||
contact.setSystemAccount(null);
|
updateRosterUi();
|
||||||
boolean needsCacheClean = contact.setPhotoUri(null);
|
});
|
||||||
needsCacheClean |= contact.setSystemName(null);
|
mQuickConversationsService.considerSync();
|
||||||
if (needsCacheClean) {
|
});
|
||||||
getAvatarService().clear(contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(Config.LOGTAG, "finished merging phone contacts");
|
|
||||||
mShortcutService.refresh(mInitialAddressbookSyncCompleted.compareAndSet(false, true));
|
|
||||||
updateRosterUi();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,55 +24,6 @@ public class PhoneHelper {
|
||||||
return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadPhoneContacts(Context context, final OnPhoneContactsLoadedListener listener) {
|
|
||||||
final List<Bundle> phoneContacts = new ArrayList<>();
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
|
||||||
&& context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
listener.onPhoneContactsLoaded(phoneContacts);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
|
|
||||||
ContactsContract.Data.DISPLAY_NAME,
|
|
||||||
ContactsContract.Data.PHOTO_URI,
|
|
||||||
ContactsContract.Data.LOOKUP_KEY,
|
|
||||||
ContactsContract.CommonDataKinds.Im.DATA};
|
|
||||||
|
|
||||||
final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
|
|
||||||
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
|
|
||||||
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
|
|
||||||
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
|
|
||||||
+ "\")";
|
|
||||||
|
|
||||||
CursorLoader mCursorLoader = new NotThrowCursorLoader(context,
|
|
||||||
ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
|
|
||||||
null);
|
|
||||||
mCursorLoader.registerListener(0, (arg0, c) -> {
|
|
||||||
if (c != null) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
Bundle contact = new Bundle();
|
|
||||||
contact.putInt("phoneid", c.getInt(c.getColumnIndex(ContactsContract.Data._ID)));
|
|
||||||
contact.putString("displayname", c.getString(c.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
|
|
||||||
contact.putString("photouri", c.getString(c.getColumnIndex(ContactsContract.Data.PHOTO_URI)));
|
|
||||||
contact.putString("lookup", c.getString(c.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
|
|
||||||
contact.putString("jid", c.getString(c.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
|
|
||||||
phoneContacts.add(contact);
|
|
||||||
}
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onPhoneContactsLoaded(phoneContacts);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
mCursorLoader.startLoading();
|
|
||||||
} catch (RejectedExecutionException e) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onPhoneContactsLoaded(phoneContacts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri getProfilePictureUri(Context context) {
|
public static Uri getProfilePictureUri(Context context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -104,22 +55,4 @@ public class PhoneHelper {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NotThrowCursorLoader extends CursorLoader {
|
|
||||||
|
|
||||||
private NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) {
|
|
||||||
super(c, u, p, s, sa, so);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor loadInBackground() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (super.loadInBackground());
|
|
||||||
} catch (Throwable e) {
|
|
||||||
return (null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,13 @@ package eu.siacs.conversations.utils;
|
||||||
public class ReplacingSerialSingleThreadExecutor extends SerialSingleThreadExecutor {
|
public class ReplacingSerialSingleThreadExecutor extends SerialSingleThreadExecutor {
|
||||||
|
|
||||||
public ReplacingSerialSingleThreadExecutor(String name) {
|
public ReplacingSerialSingleThreadExecutor(String name) {
|
||||||
super(name, false);
|
super(name);
|
||||||
}
|
|
||||||
|
|
||||||
public ReplacingSerialSingleThreadExecutor(boolean prepareLooper) {
|
|
||||||
super(ReplacingSerialSingleThreadExecutor.class.getName(), prepareLooper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void execute(final Runnable r) {
|
public synchronized void execute(final Runnable r) {
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
if (active != null && active instanceof Cancellable) {
|
if (active instanceof Cancellable) {
|
||||||
((Cancellable) active).cancel();
|
((Cancellable) active).cancel();
|
||||||
}
|
}
|
||||||
super.execute(r);
|
super.execute(r);
|
||||||
|
@ -21,7 +17,7 @@ public class ReplacingSerialSingleThreadExecutor extends SerialSingleThreadExecu
|
||||||
|
|
||||||
public synchronized void cancelRunningTasks() {
|
public synchronized void cancelRunningTasks() {
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
if (active != null && active instanceof Cancellable) {
|
if (active instanceof Cancellable) {
|
||||||
((Cancellable) active).cancel();
|
((Cancellable) active).cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class ReplacingTaskManager {
|
||||||
synchronized (this.executors) {
|
synchronized (this.executors) {
|
||||||
executor = this.executors.get(account);
|
executor = this.executors.get(account);
|
||||||
if (executor == null) {
|
if (executor == null) {
|
||||||
executor = new ReplacingSerialSingleThreadExecutor(false);
|
executor = new ReplacingSerialSingleThreadExecutor(ReplacingTaskManager.class.getSimpleName());
|
||||||
this.executors.put(account, executor);
|
this.executors.put(account, executor);
|
||||||
}
|
}
|
||||||
executor.execute(runnable);
|
executor.execute(runnable);
|
||||||
|
|
|
@ -1,73 +1,64 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
|
|
||||||
|
|
||||||
public class SerialSingleThreadExecutor implements Executor {
|
public class SerialSingleThreadExecutor implements Executor {
|
||||||
|
|
||||||
private final Executor executor = Executors.newSingleThreadExecutor();
|
final ArrayDeque<Runnable> tasks = new ArrayDeque<>();
|
||||||
final ArrayDeque<Runnable> tasks = new ArrayDeque<>();
|
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||||
protected Runnable active;
|
private final String name;
|
||||||
private final String name;
|
protected Runnable active;
|
||||||
|
|
||||||
public SerialSingleThreadExecutor(String name) {
|
|
||||||
this(name, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
SerialSingleThreadExecutor(String name, boolean prepareLooper) {
|
public SerialSingleThreadExecutor(String name) {
|
||||||
if (prepareLooper) {
|
this.name = name;
|
||||||
execute(Looper::prepare);
|
}
|
||||||
}
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void execute(final Runnable r) {
|
public synchronized void execute(final Runnable r) {
|
||||||
tasks.offer(new Runner(r));
|
tasks.offer(new Runner(r));
|
||||||
if (active == null) {
|
if (active == null) {
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void scheduleNext() {
|
private synchronized void scheduleNext() {
|
||||||
if ((active = tasks.poll()) != null) {
|
if ((active = tasks.poll()) != null) {
|
||||||
executor.execute(active);
|
executor.execute(active);
|
||||||
int remaining = tasks.size();
|
int remaining = tasks.size();
|
||||||
if (remaining > 0) {
|
if (remaining > 0) {
|
||||||
Log.d(Config.LOGTAG,remaining+" remaining tasks on executor '"+name+"'");
|
Log.d(Config.LOGTAG, remaining + " remaining tasks on executor '" + name + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Runner implements Runnable, Cancellable {
|
private class Runner implements Runnable, Cancellable {
|
||||||
|
|
||||||
private final Runnable runnable;
|
private final Runnable runnable;
|
||||||
|
|
||||||
private Runner(Runnable runnable) {
|
private Runner(Runnable runnable) {
|
||||||
this.runnable = runnable;
|
this.runnable = runnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
if (runnable instanceof Cancellable) {
|
if (runnable instanceof Cancellable) {
|
||||||
((Cancellable) runnable).cancel();
|
((Cancellable) runnable).cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
} finally {
|
} finally {
|
||||||
scheduleNext();
|
scheduleNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package eu.siacs.conversations.android;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
|
||||||
|
import io.michaelrocks.libphonenumber.android.NumberParseException;
|
||||||
|
|
||||||
|
public class PhoneNumberContact extends AbstractPhoneContact {
|
||||||
|
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
public String getPhoneNumber() {
|
||||||
|
return phoneNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PhoneNumberContact(Context context, Cursor cursor) throws IllegalArgumentException {
|
||||||
|
super(cursor);
|
||||||
|
try {
|
||||||
|
this.phoneNumber = PhoneNumberUtilWrapper.normalize(context,cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
|
||||||
|
} catch (NumberParseException | NullPointerException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(Context context, OnPhoneContactsLoaded<PhoneNumberContact> callback) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
callback.onPhoneContactsLoaded(Collections.emptyList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
|
||||||
|
ContactsContract.Data.DISPLAY_NAME,
|
||||||
|
ContactsContract.Data.PHOTO_URI,
|
||||||
|
ContactsContract.Data.LOOKUP_KEY,
|
||||||
|
ContactsContract.CommonDataKinds.Phone.NUMBER};
|
||||||
|
final Cursor cursor;
|
||||||
|
try {
|
||||||
|
cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.onPhoneContactsLoaded(Collections.emptyList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final HashMap<String, PhoneNumberContact> contacts = new HashMap<>();
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
final PhoneNumberContact contact = new PhoneNumberContact(context, cursor);
|
||||||
|
final PhoneNumberContact preexisting = contacts.get(contact.getPhoneNumber());
|
||||||
|
if (preexisting == null || preexisting.rating() < contact.rating()) {
|
||||||
|
contacts.put(contact.getPhoneNumber(), contact);
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to create phone contact");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
callback.onPhoneContactsLoaded(contacts.values());
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.android.PhoneNumberContact;
|
||||||
import eu.siacs.conversations.crypto.sasl.Plain;
|
import eu.siacs.conversations.crypto.sasl.Plain;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.utils.AccountUtils;
|
import eu.siacs.conversations.utils.AccountUtils;
|
||||||
|
@ -264,6 +265,14 @@ public class QuickConversationsService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void considerSync() {
|
||||||
|
PhoneNumberContact.load(service, contacts -> {
|
||||||
|
for(PhoneNumberContact c : contacts) {
|
||||||
|
Log.d(Config.LOGTAG, "Display Name=" + c.getDisplayName() + ", number=" + c.getPhoneNumber()+", uri="+c.getLookupUri());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnVerificationRequested {
|
public interface OnVerificationRequested {
|
||||||
void onVerificationRequestFailed(int code);
|
void onVerificationRequestFailed(int code);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue