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
	
	Block a user