|
@@ -29,15 +29,15 @@ import android.content.ContentProviderOperation;
|
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentResolver;
|
|
|
import android.content.ContentUris;
|
|
import android.content.ContentUris;
|
|
|
import android.content.Context;
|
|
import android.content.Context;
|
|
|
-import android.content.OperationApplicationException;
|
|
|
|
|
|
|
+import android.content.Intent;
|
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.PackageManager;
|
|
|
import android.database.Cursor;
|
|
import android.database.Cursor;
|
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Bitmap;
|
|
|
import android.graphics.drawable.Drawable;
|
|
import android.graphics.drawable.Drawable;
|
|
|
import android.net.Uri;
|
|
import android.net.Uri;
|
|
|
import android.os.Build;
|
|
import android.os.Build;
|
|
|
-import android.os.RemoteException;
|
|
|
|
|
import android.provider.ContactsContract;
|
|
import android.provider.ContactsContract;
|
|
|
|
|
+import android.widget.Toast;
|
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
@@ -48,28 +48,30 @@ import java.util.HashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
import java.util.TreeMap;
|
|
import java.util.TreeMap;
|
|
|
|
|
+import java.util.regex.PatternSyntaxException;
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.NonNull;
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.Nullable;
|
|
|
|
|
+import androidx.annotation.RequiresPermission;
|
|
|
|
|
+import androidx.annotation.WorkerThread;
|
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
|
import androidx.core.util.Pair;
|
|
import androidx.core.util.Pair;
|
|
|
-import ch.threema.app.BuildConfig;
|
|
|
|
|
import ch.threema.app.R;
|
|
import ch.threema.app.R;
|
|
|
import ch.threema.app.ThreemaApplication;
|
|
import ch.threema.app.ThreemaApplication;
|
|
|
import ch.threema.app.exceptions.FileSystemNotPresentException;
|
|
import ch.threema.app.exceptions.FileSystemNotPresentException;
|
|
|
import ch.threema.app.managers.ServiceManager;
|
|
import ch.threema.app.managers.ServiceManager;
|
|
|
import ch.threema.app.services.FileService;
|
|
import ch.threema.app.services.FileService;
|
|
|
import ch.threema.app.services.UserService;
|
|
import ch.threema.app.services.UserService;
|
|
|
|
|
+import ch.threema.base.ThreemaException;
|
|
|
import ch.threema.storage.models.ContactModel;
|
|
import ch.threema.storage.models.ContactModel;
|
|
|
-import java8.util.J8Arrays;
|
|
|
|
|
-import java8.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
import static ch.threema.storage.models.ContactModel.DEFAULT_ANDROID_CONTACT_AVATAR_EXPIRY;
|
|
import static ch.threema.storage.models.ContactModel.DEFAULT_ANDROID_CONTACT_AVATAR_EXPIRY;
|
|
|
|
|
|
|
|
public class AndroidContactUtil {
|
|
public class AndroidContactUtil {
|
|
|
private static final Logger logger = LoggerFactory.getLogger(AndroidContactUtil.class);
|
|
private static final Logger logger = LoggerFactory.getLogger(AndroidContactUtil.class);
|
|
|
|
|
+ private UserService userService;
|
|
|
|
|
+ private FileService fileService;
|
|
|
|
|
|
|
|
- // Singleton stuff
|
|
|
|
|
private static AndroidContactUtil sInstance = null;
|
|
private static AndroidContactUtil sInstance = null;
|
|
|
|
|
|
|
|
public static synchronized AndroidContactUtil getInstance() {
|
|
public static synchronized AndroidContactUtil getInstance() {
|
|
@@ -79,12 +81,27 @@ public class AndroidContactUtil {
|
|
|
return sInstance;
|
|
return sInstance;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static final String[] NAME_PROJECTION = new String[]{
|
|
|
|
|
|
|
+ private AndroidContactUtil() {
|
|
|
|
|
+ ServiceManager serviceManager = ThreemaApplication.getServiceManager();
|
|
|
|
|
+
|
|
|
|
|
+ if (serviceManager != null) {
|
|
|
|
|
+ this.userService = serviceManager.getUserService();
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.fileService = serviceManager.getFileService();
|
|
|
|
|
+ } catch (FileSystemNotPresentException ignored) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final String[] NAME_PROJECTION = new String[] {
|
|
|
ContactsContract.Contacts.DISPLAY_NAME,
|
|
ContactsContract.Contacts.DISPLAY_NAME,
|
|
|
ContactsContract.Contacts.SORT_KEY_ALTERNATIVE,
|
|
ContactsContract.Contacts.SORT_KEY_ALTERNATIVE,
|
|
|
ContactsContract.Contacts._ID
|
|
ContactsContract.Contacts._ID
|
|
|
};
|
|
};
|
|
|
- private static final String[] LOOKUP_KEY_PROJECTION = new String[]{ContactsContract.Contacts.LOOKUP_KEY};
|
|
|
|
|
|
|
+
|
|
|
|
|
+ private static final String[] RAW_CONTACT_PROJECTION = new String[] {
|
|
|
|
|
+ ContactsContract.RawContacts.CONTACT_ID,
|
|
|
|
|
+ ContactsContract.RawContacts.SYNC1,
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
private static final String[] STRUCTURED_NAME_FIELDS = new String[] {
|
|
private static final String[] STRUCTURED_NAME_FIELDS = new String[] {
|
|
|
ContactsContract.CommonDataKinds.StructuredName.PREFIX,
|
|
ContactsContract.CommonDataKinds.StructuredName.PREFIX,
|
|
@@ -95,423 +112,88 @@ public class AndroidContactUtil {
|
|
|
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
|
|
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- private interface JoinContactQuery {
|
|
|
|
|
- int _ID = 0;
|
|
|
|
|
- int CONTACT_ID = 1;
|
|
|
|
|
- int DISPLAY_NAME_SOURCE = 2;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
private final ContentResolver contentResolver = ThreemaApplication.getAppContext().getContentResolver();
|
|
private final ContentResolver contentResolver = ThreemaApplication.getAppContext().getContentResolver();
|
|
|
- private Map<String, String> identityLookupCache = null;
|
|
|
|
|
- private final Object identityLookupCacheLock = new Object();
|
|
|
|
|
|
|
|
|
|
private @Nullable Account getAccount() {
|
|
private @Nullable Account getAccount() {
|
|
|
- ServiceManager serviceManager = ThreemaApplication.getServiceManager();
|
|
|
|
|
- if(serviceManager != null) {
|
|
|
|
|
- UserService userService = serviceManager.getUserService();
|
|
|
|
|
- if(userService != null) {
|
|
|
|
|
- return userService.getAccount();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private FileService getFileService() {
|
|
|
|
|
- ServiceManager serviceManager = ThreemaApplication.getServiceManager();
|
|
|
|
|
- if(serviceManager != null) {
|
|
|
|
|
- try {
|
|
|
|
|
- return serviceManager.getFileService();
|
|
|
|
|
- } catch (FileSystemNotPresentException ignored) {}
|
|
|
|
|
- }
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private Long getContactId(String lookupKey) {
|
|
|
|
|
- if(TestUtil.empty(lookupKey)) {
|
|
|
|
|
|
|
+ if (userService == null) {
|
|
|
|
|
+ logger.info("UserService not available");
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
- Long id = null;
|
|
|
|
|
- Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey);
|
|
|
|
|
- if(uri != null) {
|
|
|
|
|
- Cursor cursor = this.contentResolver.query(
|
|
|
|
|
- uri,
|
|
|
|
|
- new String[]{
|
|
|
|
|
- ContactsContract.Contacts._ID
|
|
|
|
|
- },
|
|
|
|
|
- null,
|
|
|
|
|
- null,
|
|
|
|
|
- null
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if(cursor != null) {
|
|
|
|
|
- if(cursor.moveToFirst()) {
|
|
|
|
|
- id = cursor.getLong(0);
|
|
|
|
|
- }
|
|
|
|
|
- cursor.close();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return id;
|
|
|
|
|
|
|
+ return userService.getAccount();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public boolean isThreemaAndroidContactJoined(String identity, String androidContactLookupkey) {
|
|
|
|
|
- String lookupKey = this.getRawContactLookupKeyByIdentity(identity);
|
|
|
|
|
-
|
|
|
|
|
- if(!TestUtil.empty(lookupKey)) {
|
|
|
|
|
- Long threemaContactId = this.getContactId(lookupKey);
|
|
|
|
|
- Long androidContactId = this.getContactId(androidContactLookupkey);
|
|
|
|
|
|
|
+ private static class ContactName {
|
|
|
|
|
+ final String firstName;
|
|
|
|
|
+ final String lastName;
|
|
|
|
|
|
|
|
- return TestUtil.compare(threemaContactId, androidContactId);
|
|
|
|
|
|
|
+ public ContactName(String firstName, String lastName) {
|
|
|
|
|
+ this.firstName = firstName;
|
|
|
|
|
+ this.lastName = lastName;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return false;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
- * copy of ContactSaveService joinContacts (Google Code)
|
|
|
|
|
|
|
+ * Return a valid uri to the given contact that can be used to build an intent for the contact app
|
|
|
|
|
+ * It is safe to call this method if permission to access contacts is not granted
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contactModel ContactModel for which to get the Android contact URI
|
|
|
|
|
+ * @return a valid uri pointing to the android contact or null if permission was not granted, no android contact is linked or android contact could not be looked up
|
|
|
*/
|
|
*/
|
|
|
- private boolean joinContacts(long contactId1, long contactId2) {
|
|
|
|
|
- if (contactId1 == -1 || contactId2 == -1) {
|
|
|
|
|
- logger.debug("Invalid arguments for joinContacts request");
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- final ContentResolver resolver = this.contentResolver;
|
|
|
|
|
-
|
|
|
|
|
- // Load raw contact IDs for all raw contacts involved - currently edited and selected
|
|
|
|
|
- // in the join UIs
|
|
|
|
|
- Cursor c = resolver.query(ContactsContract.RawContacts.CONTENT_URI,
|
|
|
|
|
- new String[] {
|
|
|
|
|
- ContactsContract.RawContacts._ID,
|
|
|
|
|
- ContactsContract.RawContacts.CONTACT_ID,
|
|
|
|
|
- ContactsContract.RawContacts.DISPLAY_NAME_SOURCE
|
|
|
|
|
- },
|
|
|
|
|
- ContactsContract.RawContacts.CONTACT_ID + "=? OR " + ContactsContract.RawContacts.CONTACT_ID + "=?",
|
|
|
|
|
- new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null);
|
|
|
|
|
-
|
|
|
|
|
- long rawContactIds[];
|
|
|
|
|
- long verifiedNameRawContactId = -1;
|
|
|
|
|
-
|
|
|
|
|
- if(c != null) {
|
|
|
|
|
- try {
|
|
|
|
|
- if (c.getCount() == 0) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- int maxDisplayNameSource = -1;
|
|
|
|
|
- rawContactIds = new long[c.getCount()];
|
|
|
|
|
- for (int i = 0; i < rawContactIds.length; i++) {
|
|
|
|
|
- c.moveToPosition(i);
|
|
|
|
|
- long rawContactId = c.getLong(JoinContactQuery._ID);
|
|
|
|
|
- rawContactIds[i] = rawContactId;
|
|
|
|
|
- int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
|
|
|
|
|
- if (nameSource > maxDisplayNameSource) {
|
|
|
|
|
- maxDisplayNameSource = nameSource;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- for (int i = 0; i < rawContactIds.length; i++) {
|
|
|
|
|
- c.moveToPosition(i);
|
|
|
|
|
- if (c.getLong(JoinContactQuery.CONTACT_ID) == contactId1) {
|
|
|
|
|
- int nameSource = c.getInt(JoinContactQuery.DISPLAY_NAME_SOURCE);
|
|
|
|
|
- if (nameSource == maxDisplayNameSource
|
|
|
|
|
- && (verifiedNameRawContactId == -1)) {
|
|
|
|
|
- verifiedNameRawContactId = c.getLong(JoinContactQuery._ID);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } finally {
|
|
|
|
|
- c.close();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // For each pair of raw contacts, insert an aggregation exception
|
|
|
|
|
- ArrayList<ContentProviderOperation> operations = new ArrayList<>();
|
|
|
|
|
- for (int i = 0; i < rawContactIds.length; i++) {
|
|
|
|
|
- for (int j = 0; j < rawContactIds.length; j++) {
|
|
|
|
|
- if (i != j) {
|
|
|
|
|
- ContentProviderOperation.Builder builder =
|
|
|
|
|
- ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI);
|
|
|
|
|
- builder.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER);
|
|
|
|
|
- builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, rawContactIds[i]);
|
|
|
|
|
- builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, rawContactIds[j]);
|
|
|
|
|
- operations.add(builder.build());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- //mark as SUPER PRIMARY
|
|
|
|
|
- if (verifiedNameRawContactId != -1) {
|
|
|
|
|
- operations.add(
|
|
|
|
|
- ContentProviderOperation.newUpdate(ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, verifiedNameRawContactId))
|
|
|
|
|
- .withValue(ContactsContract.Data.IS_SUPER_PRIMARY, 1)
|
|
|
|
|
- .build());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- boolean success = false;
|
|
|
|
|
- // split aggregation excemptions into chunks of 200 operations
|
|
|
|
|
- final int chunkSize = 200;
|
|
|
|
|
- int size = operations.size();
|
|
|
|
|
-
|
|
|
|
|
- for (int i = 0; i < size; i += chunkSize) {
|
|
|
|
|
- int end = Math.min(size, i + chunkSize) - 1;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- resolver.applyBatch(ContactsContract.AUTHORITY, new ArrayList<>(operations.subList(i, end)));
|
|
|
|
|
- success = true;
|
|
|
|
|
- } catch (RemoteException e) {
|
|
|
|
|
- logger.error("RemoteException: Failed to apply aggregation exception batch", e);
|
|
|
|
|
- } catch (OperationApplicationException e) {
|
|
|
|
|
- logger.error("OperationApplicationException: Failed to apply aggregation exception batch", e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return success;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public boolean joinThreemaAndroidContact(String identity, String androidContactLookupkey) {
|
|
|
|
|
- boolean success = false;
|
|
|
|
|
-
|
|
|
|
|
- String lookupKey = this.getRawContactLookupKeyByIdentity(identity);
|
|
|
|
|
-
|
|
|
|
|
- if(!TestUtil.empty(lookupKey)) {
|
|
|
|
|
- Long threemaContactId = this.getContactId(lookupKey);
|
|
|
|
|
- Long androidContactId = this.getContactId(androidContactLookupkey);
|
|
|
|
|
- if (TestUtil.required(threemaContactId, androidContactId)) {
|
|
|
|
|
- success = this.joinContacts(androidContactId, threemaContactId);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return success;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public boolean splitThreemaAndroidContact(String identity, String androidContactLookupkey) {
|
|
|
|
|
- boolean success = false;
|
|
|
|
|
- Account account = this.getAccount();
|
|
|
|
|
- if(account == null) {
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public Uri getAndroidContactUri(@Nullable ContactModel contactModel) {
|
|
|
|
|
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
|
|
|
|
+ ContextCompat.checkSelfPermission(ThreemaApplication.getAppContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- String lookupKey = this.getRawContactLookupKeyByIdentity(identity);
|
|
|
|
|
-
|
|
|
|
|
- if(!TestUtil.empty(lookupKey)) {
|
|
|
|
|
- Long threemaContactId = this.getContactId(lookupKey);
|
|
|
|
|
- Long androidContactId = this.getContactId(androidContactLookupkey);
|
|
|
|
|
-
|
|
|
|
|
- //are the same... ok
|
|
|
|
|
- if(TestUtil.compare(threemaContactId, androidContactId)) {
|
|
|
|
|
- Cursor cursor = this.contentResolver.query(
|
|
|
|
|
- ContactsContract.RawContacts.CONTENT_URI,
|
|
|
|
|
- new String[]{
|
|
|
|
|
- ContactsContract.RawContacts._ID,
|
|
|
|
|
- ContactsContract.RawContacts.ACCOUNT_NAME,
|
|
|
|
|
- ContactsContract.RawContacts.ACCOUNT_TYPE,
|
|
|
|
|
- ContactsContract.RawContacts.SYNC1
|
|
|
|
|
- },
|
|
|
|
|
- ContactsContract.RawContacts.CONTACT_ID + "=?",
|
|
|
|
|
- new String[]{String.valueOf(threemaContactId)},
|
|
|
|
|
- null);
|
|
|
|
|
-
|
|
|
|
|
- if(cursor != null) {
|
|
|
|
|
- List<Long> rawContactIds = new ArrayList<>();
|
|
|
|
|
- List<Long> accountContactIds = new ArrayList<>();
|
|
|
|
|
-
|
|
|
|
|
- while(cursor.moveToNext()) {
|
|
|
|
|
- long id = cursor.getLong(0);
|
|
|
|
|
- String accountName = cursor.getString(1);
|
|
|
|
|
- String accountType = cursor.getString(2);
|
|
|
|
|
- String accountIdentity = cursor.getString(3);
|
|
|
|
|
-
|
|
|
|
|
- if(TestUtil.compare(accountName, account.name) &&
|
|
|
|
|
- TestUtil.compare(accountType, account.type) &&
|
|
|
|
|
- TestUtil.compare(accountIdentity, identity)) {
|
|
|
|
|
- accountContactIds.add(id);
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- rawContactIds.add(id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- cursor.close();
|
|
|
|
|
- // For each pair of raw contacts, insert an aggregation exception
|
|
|
|
|
- ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
|
|
|
|
|
-
|
|
|
|
|
- for (long joinId: rawContactIds) {
|
|
|
|
|
- for(long accountId: accountContactIds) {
|
|
|
|
|
- if (joinId != accountId) {
|
|
|
|
|
- try {
|
|
|
|
|
- ContentProviderOperation.Builder builder =
|
|
|
|
|
- ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI);
|
|
|
|
|
- builder.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_SEPARATE);
|
|
|
|
|
- builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, joinId);
|
|
|
|
|
- builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, accountId);
|
|
|
|
|
- operations.add(builder.build());
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- logger.error("Exception", e);
|
|
|
|
|
- }
|
|
|
|
|
- //reset name of threema account
|
|
|
|
|
|
|
+ if (contactModel != null) {
|
|
|
|
|
+ String contactLookupKey = contactModel.getAndroidContactLookupKey();
|
|
|
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!TestUtil.empty(contactLookupKey)) {
|
|
|
|
|
+ Uri contactLookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, contactLookupKey);
|
|
|
|
|
|
|
|
|
|
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
|
|
try {
|
|
try {
|
|
|
- this.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
|
|
|
|
|
- success = true;
|
|
|
|
|
- } catch (RemoteException | OperationApplicationException e) {
|
|
|
|
|
|
|
+ contactLookupUri = ContactsContract.Contacts.lookupContact(contentResolver, contactLookupUri);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
logger.error("Exception", e);
|
|
logger.error("Exception", e);
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- //reset name to identity
|
|
|
|
|
-// this.updateNameOfThreemaContact(identity, identity, true);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return success;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public void startCache() {
|
|
|
|
|
- this.stopCache();
|
|
|
|
|
- this.identityLookupCache = new HashMap<String, String>();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public void stopCache() {
|
|
|
|
|
- if(this.identityLookupCache != null) {
|
|
|
|
|
- synchronized (this.identityLookupCacheLock) {
|
|
|
|
|
- this.identityLookupCache.clear();
|
|
|
|
|
- this.identityLookupCache = null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public Drawable getAccountIcon(String identity) {
|
|
|
|
|
- Account myAccount = this.getAccount();
|
|
|
|
|
- if(myAccount == null) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- String lookupKey = this.getRawContactLookupKeyByIdentity(identity);
|
|
|
|
|
- if(TestUtil.empty(lookupKey)) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- AccountManager accountManager = AccountManager.get(ThreemaApplication.getAppContext());
|
|
|
|
|
- List<AuthenticatorDescription> descriptions = new ArrayList<AuthenticatorDescription>();
|
|
|
|
|
-
|
|
|
|
|
- //add google as first!
|
|
|
|
|
- for(AuthenticatorDescription des: accountManager.getAuthenticatorTypes()) {
|
|
|
|
|
-
|
|
|
|
|
- if("com.google".equals(des.type)) {
|
|
|
|
|
- //first
|
|
|
|
|
- descriptions.add(0, des);
|
|
|
|
|
- }
|
|
|
|
|
- else if(!TestUtil.compare(BuildConfig.APPLICATION_ID, des.type)) {
|
|
|
|
|
- descriptions.add(des);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- AuthenticatorDescription[] descriptionResult = new AuthenticatorDescription[descriptions.size()];
|
|
|
|
|
- Long contactId = this.getContactId(lookupKey);
|
|
|
|
|
- Drawable fallback = null;
|
|
|
|
|
-
|
|
|
|
|
- //select joined contact (excluding threema contact)
|
|
|
|
|
- if(contactId != null) {
|
|
|
|
|
- Cursor cursor = this.contentResolver.query(
|
|
|
|
|
- ContactsContract.RawContacts.CONTENT_URI,
|
|
|
|
|
- new String[]{
|
|
|
|
|
- ContactsContract.RawContacts.ACCOUNT_TYPE
|
|
|
|
|
- },
|
|
|
|
|
- ContactsContract.RawContacts.CONTACT_ID + " = ? AND "
|
|
|
|
|
- + ContactsContract.RawContacts.ACCOUNT_TYPE + " != ?",
|
|
|
|
|
- new String[]{
|
|
|
|
|
- String.valueOf(contactId),
|
|
|
|
|
- BuildConfig.APPLICATION_ID
|
|
|
|
|
- },
|
|
|
|
|
- null
|
|
|
|
|
- );
|
|
|
|
|
- if(cursor != null) {
|
|
|
|
|
-
|
|
|
|
|
- while(cursor.moveToNext()) {
|
|
|
|
|
- String type = cursor.getString(0);
|
|
|
|
|
-
|
|
|
|
|
- for(int n = 0; n < descriptions.size(); n++) {
|
|
|
|
|
- AuthenticatorDescription description = descriptions.get(n);
|
|
|
|
|
- if (description.type.equals(type)) {
|
|
|
|
|
- descriptionResult[n] = description;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- cursor.close();
|
|
|
|
|
|
|
+ return contactLookupUri;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- for(AuthenticatorDescription des: descriptionResult) {
|
|
|
|
|
- if(des != null) {
|
|
|
|
|
- PackageManager pm = ThreemaApplication.getAppContext().getPackageManager();
|
|
|
|
|
- Drawable drawable = pm.getDrawable(des.packageName, des.iconId, null);
|
|
|
|
|
- if(drawable != null) {
|
|
|
|
|
- if(des.type.equals(myAccount.type)) {
|
|
|
|
|
- fallback = drawable;
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- return drawable;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- //if no icon found, display the icon of the phone or contacts app
|
|
|
|
|
- if( fallback == null) {
|
|
|
|
|
- for (String namespace : new String[]{
|
|
|
|
|
- "com.android.phone",
|
|
|
|
|
- "com.android.providers.contacts"
|
|
|
|
|
- }) {
|
|
|
|
|
- try {
|
|
|
|
|
- fallback = ThreemaApplication.getAppContext().getPackageManager().getApplicationIcon(namespace);
|
|
|
|
|
- break;
|
|
|
|
|
- } catch (PackageManager.NameNotFoundException x) {
|
|
|
|
|
- //
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return fallback;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private class ContactName {
|
|
|
|
|
- final String firstName;
|
|
|
|
|
- final String lastName;
|
|
|
|
|
-
|
|
|
|
|
- public ContactName(String firstName, String lastName) {
|
|
|
|
|
- this.firstName = firstName;
|
|
|
|
|
- this.lastName = lastName;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Update the avatar for the specified contact from Android's contact database, if any
|
|
|
|
|
+ * If there's no avatar for this Android contact, any current avatar on file will be deleted
|
|
|
|
|
+ *
|
|
|
|
|
+ * It is safe to call this method even if permission to read contacts is not given
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contactModel ContactModel
|
|
|
|
|
+ * @return true if setting or deleting the avatar was successful, false otherwise
|
|
|
|
|
+ */
|
|
|
public boolean updateAvatarByAndroidContact(ContactModel contactModel) {
|
|
public boolean updateAvatarByAndroidContact(ContactModel contactModel) {
|
|
|
- FileService fileService = getFileService();
|
|
|
|
|
-
|
|
|
|
|
if (fileService == null) {
|
|
if (fileService == null) {
|
|
|
|
|
+ logger.info("FileService not available");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- String androidContactId = contactModel.getAndroidContactId();
|
|
|
|
|
|
|
+ String androidContactId = contactModel.getAndroidContactLookupKey();
|
|
|
|
|
|
|
|
if(TestUtil.empty(androidContactId)) {
|
|
if(TestUtil.empty(androidContactId)) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Uri contactUri = ContactUtil.getAndroidContactUri(ThreemaApplication.getAppContext(), contactModel);
|
|
|
|
|
|
|
+ Uri contactUri = getAndroidContactUri(contactModel);
|
|
|
if (contactUri == null) {
|
|
if (contactUri == null) {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Bitmap bitmap = null;
|
|
Bitmap bitmap = null;
|
|
|
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
|
|
|
|
|
- ContextCompat.checkSelfPermission(ThreemaApplication.getAppContext(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if (ConfigUtils.isPermissionGranted(ThreemaApplication.getAppContext(), Manifest.permission.READ_CONTACTS)) {
|
|
|
bitmap = AvatarConverterUtil.convert(ThreemaApplication.getAppContext(), contactUri);
|
|
bitmap = AvatarConverterUtil.convert(ThreemaApplication.getAppContext(), contactUri);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -535,49 +217,71 @@ public class AndroidContactUtil {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public boolean updateNameByAndroidContact(@NonNull ContactModel contactModel) {
|
|
|
|
|
- Uri namedContactUri = ContactUtil.getAndroidContactUri(ThreemaApplication.getAppContext(), contactModel);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Update the name of this contact according to the name of the Android contact
|
|
|
|
|
+ * Note that the ContactModel needs to be saved to the ContactStore to apply the changes
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contactModel ContactModel
|
|
|
|
|
+ * @return true if setting the name was successful, false otherwise
|
|
|
|
|
+ */
|
|
|
|
|
+ @RequiresPermission(Manifest.permission.READ_CONTACTS)
|
|
|
|
|
+ public boolean updateNameByAndroidContact(@NonNull ContactModel contactModel) throws ThreemaException {
|
|
|
|
|
+ Uri namedContactUri = getAndroidContactUri(contactModel);
|
|
|
if(TestUtil.required(contactModel, namedContactUri)) {
|
|
if(TestUtil.required(contactModel, namedContactUri)) {
|
|
|
ContactName contactName = this.getContactName(namedContactUri);
|
|
ContactName contactName = this.getContactName(namedContactUri);
|
|
|
- if(TestUtil.required(contactModel, contactName)) {
|
|
|
|
|
- if(!TestUtil.compare(contactModel.getFirstName(), contactName.firstName)
|
|
|
|
|
- || !TestUtil.compare(contactModel.getLastName(), contactName.lastName)) {
|
|
|
|
|
- //changed... update
|
|
|
|
|
- contactModel.setFirstName(contactName.firstName);
|
|
|
|
|
- contactModel.setLastName(contactName.lastName);
|
|
|
|
|
- return true;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (contactName == null) {
|
|
|
|
|
+ throw new ThreemaException("Unable to get contact name");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(!TestUtil.compare(contactModel.getFirstName(), contactName.firstName)
|
|
|
|
|
+ || !TestUtil.compare(contactModel.getLastName(), contactName.lastName)) {
|
|
|
|
|
+ contactModel.setFirstName(contactName.firstName);
|
|
|
|
|
+ contactModel.setLastName(contactName.lastName);
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get the contact name for a system contact specified by the specified Uri
|
|
|
|
|
+ * First we will consider the Structured Name of the contact
|
|
|
|
|
+ * If the Structured Name is lacking either a first name, a last name, or both, we will fall back to the Display Name
|
|
|
|
|
+ * If there's still neither first nor last name available, we will resort to the alternative representation of the full name (for Western names, it is the one using the "last, first" format)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contactUri Uri pointing to the contact
|
|
|
|
|
+ * @return ContactName object containing first and last name or null if lookup failed
|
|
|
|
|
+ */
|
|
|
|
|
+ @RequiresPermission(Manifest.permission.READ_CONTACTS)
|
|
|
|
|
+ @Nullable
|
|
|
private ContactName getContactName(Uri contactUri) {
|
|
private ContactName getContactName(Uri contactUri) {
|
|
|
- if(!TestUtil.required(this.contentResolver)) {
|
|
|
|
|
|
|
+ if (!TestUtil.required(this.contentResolver)) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Cursor nameCursor = this.contentResolver.query(
|
|
|
|
|
|
|
+ ContactName contactName = null;
|
|
|
|
|
+ Cursor nameCursor = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ nameCursor = this.contentResolver.query(
|
|
|
contactUri,
|
|
contactUri,
|
|
|
NAME_PROJECTION,
|
|
NAME_PROJECTION,
|
|
|
null,
|
|
null,
|
|
|
null,
|
|
null,
|
|
|
null);
|
|
null);
|
|
|
|
|
|
|
|
- ContactName contactName = null;
|
|
|
|
|
- if(nameCursor != null) {
|
|
|
|
|
- if(nameCursor.moveToFirst()) {
|
|
|
|
|
|
|
+ if (nameCursor != null && nameCursor.moveToFirst()) {
|
|
|
long contactId = nameCursor.getLong(nameCursor.getColumnIndex(ContactsContract.Contacts._ID));
|
|
long contactId = nameCursor.getLong(nameCursor.getColumnIndex(ContactsContract.Contacts._ID));
|
|
|
- contactName = this.getContactNameFromId(contactId);
|
|
|
|
|
|
|
+ contactName = this.getContactNameFromContactId(contactId);
|
|
|
|
|
|
|
|
// fallback
|
|
// fallback
|
|
|
- if (contactName == null || (contactName.firstName == null && contactName.lastName == null)) {
|
|
|
|
|
|
|
+ if (contactName.firstName == null && contactName.lastName == null) {
|
|
|
//lastname, firstname
|
|
//lastname, firstname
|
|
|
String alternativeSortKey = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.Contacts.SORT_KEY_ALTERNATIVE));
|
|
String alternativeSortKey = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.Contacts.SORT_KEY_ALTERNATIVE));
|
|
|
|
|
|
|
|
if (!TestUtil.empty(alternativeSortKey)) {
|
|
if (!TestUtil.empty(alternativeSortKey)) {
|
|
|
String[] lastNameFirstName = alternativeSortKey.split(",");
|
|
String[] lastNameFirstName = alternativeSortKey.split(",");
|
|
|
- if (lastNameFirstName != null && lastNameFirstName.length == 2) {
|
|
|
|
|
|
|
+ if (lastNameFirstName.length == 2) {
|
|
|
String lastName = lastNameFirstName[0].trim();
|
|
String lastName = lastNameFirstName[0].trim();
|
|
|
String firstName = lastNameFirstName[1].trim();
|
|
String firstName = lastNameFirstName[1].trim();
|
|
|
|
|
|
|
@@ -585,16 +289,34 @@ public class AndroidContactUtil {
|
|
|
contactName = new ContactName(firstName, lastName);
|
|
contactName = new ContactName(firstName, lastName);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // no contact name found
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.debug("Contact not found: {}", contactUri.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (PatternSyntaxException e) {
|
|
|
|
|
+ logger.error("Exception", e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (nameCursor != null) {
|
|
|
|
|
+ nameCursor.close();
|
|
|
}
|
|
}
|
|
|
- nameCursor.close();
|
|
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return contactName;
|
|
return contactName;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private ContactName getContactNameFromId(long contactId) {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get the contact name for a system contact specified by contactId
|
|
|
|
|
+ * - First we will consider the Structured Name of the contact
|
|
|
|
|
+ * - If the Structured Name is lacking either a first name, a last name, or both, we will fall back to the Display Name
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contactId Id of the Android contact
|
|
|
|
|
+ * @return ContactName object containing first and last name
|
|
|
|
|
+ */
|
|
|
|
|
+ @RequiresPermission(Manifest.permission.READ_CONTACTS)
|
|
|
|
|
+ private @NonNull ContactName getContactNameFromContactId(long contactId) {
|
|
|
Map<String, String> structure = this.getStructuredNameByContactId(contactId);
|
|
Map<String, String> structure = this.getStructuredNameByContactId(contactId);
|
|
|
|
|
|
|
|
String firstName = structure.get(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
|
|
String firstName = structure.get(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
|
|
@@ -639,28 +361,12 @@ public class AndroidContactUtil {
|
|
|
return new ContactName(contactFirstName.toString(), contactLastName.toString());
|
|
return new ContactName(contactFirstName.toString(), contactLastName.toString());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- final Pair<String, String> firstLastName = getFirstLastNameFromDisplayName(displayName);
|
|
|
|
|
|
|
+ final Pair<String, String> firstLastName = NameUtil.getFirstLastNameFromDisplayName(displayName);
|
|
|
return new ContactName(firstLastName.first, firstLastName.second);
|
|
return new ContactName(firstLastName.first, firstLastName.second);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * Extract first and last name from display name.
|
|
|
|
|
- *
|
|
|
|
|
- * If the displayName is empty or null, then empty strings will be returned for first/last name.
|
|
|
|
|
- */
|
|
|
|
|
- private static @NonNull Pair<String, String> getFirstLastNameFromDisplayName(@Nullable String displayName) {
|
|
|
|
|
- final String[] parts = displayName == null ? null : displayName.split(" ");
|
|
|
|
|
- if (parts == null || parts.length == 0) {
|
|
|
|
|
- return new Pair<>("", "");
|
|
|
|
|
- }
|
|
|
|
|
- final String firstName = parts[0];
|
|
|
|
|
- final String lastName = J8Arrays.stream(parts)
|
|
|
|
|
- .skip(1)
|
|
|
|
|
- .collect(Collectors.joining(" "));
|
|
|
|
|
- return new Pair<>(firstName, lastName);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private Map<String, String> getStructuredNameByContactId(long id) {
|
|
|
|
|
|
|
+ @RequiresPermission(Manifest.permission.READ_CONTACTS)
|
|
|
|
|
+ private @NonNull Map<String, String> getStructuredNameByContactId(long id) {
|
|
|
Map<String, String> structuredName = new TreeMap<String, String>();
|
|
Map<String, String> structuredName = new TreeMap<String, String>();
|
|
|
|
|
|
|
|
Cursor cursor = this.contentResolver.query(
|
|
Cursor cursor = this.contentResolver.query(
|
|
@@ -686,158 +392,163 @@ public class AndroidContactUtil {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Create a raw contact for the given identity. Put the identity into the SYNC1 column and set data records for messaging and calling
|
|
|
|
|
- * @param identity
|
|
|
|
|
- * @param supportsVoiceCalls
|
|
|
|
|
- * @return LOOKUP_KEY of the newly created raw contact or null if no contact has been created
|
|
|
|
|
|
|
+ * Add ContentProviderOperations to create a raw contact for the given identity to a provided List of ContentProviderOperations.
|
|
|
|
|
+ * Put the identity into the SYNC1 column and set data records for messaging and calling
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contentProviderOperations List of ContentProviderOperations to add this operation to
|
|
|
|
|
+ * @param systemRawContactId The raw contact that matched the criteria for aggregation (i.e. email or phone number)
|
|
|
|
|
+ * @param contactModel ContactModel to create a raw contact for
|
|
|
|
|
+ * @param supportsVoiceCalls Whether the user has voice calls enabled
|
|
|
*/
|
|
*/
|
|
|
- public String createThreemaAndroidContact(String identity, boolean supportsVoiceCalls) {
|
|
|
|
|
|
|
+ @RequiresPermission(allOf = {Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
|
|
|
|
|
+ public void createThreemaRawContact(@NonNull List<ContentProviderOperation> contentProviderOperations, long systemRawContactId, @NonNull ContactModel contactModel, boolean supportsVoiceCalls) {
|
|
|
|
|
+ String identity = contactModel.getIdentity();
|
|
|
Context context = ThreemaApplication.getAppContext();
|
|
Context context = ThreemaApplication.getAppContext();
|
|
|
Account account = this.getAccount();
|
|
Account account = this.getAccount();
|
|
|
if (!TestUtil.required(account, identity)) {
|
|
if (!TestUtil.required(account, identity)) {
|
|
|
- //do nothing
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- String threemaContactLookupKey = this.getRawContactLookupKeyByIdentity(identity);
|
|
|
|
|
-
|
|
|
|
|
- //alread exist
|
|
|
|
|
- if(!TestUtil.empty(threemaContactLookupKey)) {
|
|
|
|
|
- return threemaContactLookupKey;
|
|
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- ArrayList<ContentProviderOperation> insertOperationList = new ArrayList<ContentProviderOperation>();
|
|
|
|
|
-
|
|
|
|
|
|
|
+ int backReference = contentProviderOperations.size();
|
|
|
logger.debug("Adding contact: " + identity);
|
|
logger.debug("Adding contact: " + identity);
|
|
|
|
|
|
|
|
- logger.debug(" Create our RawContact");
|
|
|
|
|
|
|
+ logger.debug("Create our RawContact");
|
|
|
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
|
|
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
|
|
|
builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
|
|
builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
|
|
|
builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
|
|
builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
|
|
|
builder.withValue(ContactsContract.RawContacts.SYNC1, identity);
|
|
builder.withValue(ContactsContract.RawContacts.SYNC1, identity);
|
|
|
- insertOperationList.add(builder.build());
|
|
|
|
|
|
|
+ contentProviderOperations.add(builder.build());
|
|
|
|
|
|
|
|
Uri insertUri = ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
|
|
Uri insertUri = ContactsContract.Data.CONTENT_URI.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
|
|
|
-/*
|
|
|
|
|
- logger.debug(" Create a Data record of type 'Nickname' for our RawContact");
|
|
|
|
|
- builder = ContentProviderOperation.newInsert(insertUri);
|
|
|
|
|
- builder.withValueBackReference(ContactsContract.CommonDataKinds.Nickname.RAW_CONTACT_ID, 0);
|
|
|
|
|
- builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE);
|
|
|
|
|
- builder.withValue(ContactsContract.CommonDataKinds.Nickname.NAME, identity);
|
|
|
|
|
- builder.withValue(ContactsContract.CommonDataKinds.Nickname.TYPE, ContactsContract.CommonDataKinds.Nickname.TYPE_CUSTOM);
|
|
|
|
|
- builder.withValue(ContactsContract.CommonDataKinds.Nickname.LABEL, context.getString(R.string.title_threemaid));
|
|
|
|
|
- insertOperationList.add(builder.build());
|
|
|
|
|
-*/
|
|
|
|
|
- logger.debug(" Create a Data record of custom type");
|
|
|
|
|
|
|
+
|
|
|
|
|
+ logger.debug("Create a Data record of custom type");
|
|
|
builder = ContentProviderOperation.newInsert(insertUri);
|
|
builder = ContentProviderOperation.newInsert(insertUri);
|
|
|
- builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
|
|
|
|
|
|
|
+ builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReference);
|
|
|
builder.withValue(ContactsContract.Data.MIMETYPE, context.getString(R.string.contacts_mime_type));
|
|
builder.withValue(ContactsContract.Data.MIMETYPE, context.getString(R.string.contacts_mime_type));
|
|
|
- //DATA1 have to be the identity to fetch in the activity
|
|
|
|
|
builder.withValue(ContactsContract.Data.DATA1, identity);
|
|
builder.withValue(ContactsContract.Data.DATA1, identity);
|
|
|
builder.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name));
|
|
builder.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name));
|
|
|
builder.withValue(ContactsContract.Data.DATA3, context.getString(R.string.threema_message_to, identity));
|
|
builder.withValue(ContactsContract.Data.DATA3, context.getString(R.string.threema_message_to, identity));
|
|
|
builder.withYieldAllowed(true);
|
|
builder.withYieldAllowed(true);
|
|
|
- insertOperationList.add(builder.build());
|
|
|
|
|
|
|
+ contentProviderOperations.add(builder.build());
|
|
|
|
|
|
|
|
if (supportsVoiceCalls) {
|
|
if (supportsVoiceCalls) {
|
|
|
- logger.debug(" Create a Data record of custom type for call");
|
|
|
|
|
|
|
+ logger.debug("Create a Data record of custom type for call");
|
|
|
builder = ContentProviderOperation.newInsert(insertUri);
|
|
builder = ContentProviderOperation.newInsert(insertUri);
|
|
|
- builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
|
|
|
|
|
|
|
+ builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReference);
|
|
|
builder.withValue(ContactsContract.Data.MIMETYPE, context.getString(R.string.call_mime_type));
|
|
builder.withValue(ContactsContract.Data.MIMETYPE, context.getString(R.string.call_mime_type));
|
|
|
builder.withValue(ContactsContract.Data.DATA1, identity);
|
|
builder.withValue(ContactsContract.Data.DATA1, identity);
|
|
|
builder.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name));
|
|
builder.withValue(ContactsContract.Data.DATA2, context.getString(R.string.app_name));
|
|
|
builder.withValue(ContactsContract.Data.DATA3, context.getString(R.string.threema_call_with, identity));
|
|
builder.withValue(ContactsContract.Data.DATA3, context.getString(R.string.threema_call_with, identity));
|
|
|
builder.withYieldAllowed(true);
|
|
builder.withYieldAllowed(true);
|
|
|
- insertOperationList.add(builder.build());
|
|
|
|
|
|
|
+ contentProviderOperations.add(builder.build());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- context.getContentResolver().applyBatch(
|
|
|
|
|
- ContactsContract.AUTHORITY,
|
|
|
|
|
- insertOperationList);
|
|
|
|
|
|
|
+ builder = ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI);
|
|
|
|
|
+ builder.withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, systemRawContactId);
|
|
|
|
|
+ builder.withValueBackReference(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, backReference);
|
|
|
|
|
+ builder.withValue(ContactsContract.AggregationExceptions.TYPE, ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER);
|
|
|
|
|
+ contentProviderOperations.add(builder.build());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //get the created or updated contact id
|
|
|
|
|
- return this.getRawContactLookupKeyByIdentity(identity);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- logger.error("Error during raw contact creation! ", e);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Delete the raw contact where the given identity matches the entry in the contact's SYNC1 column
|
|
|
|
|
+ * It's safe to call this method without contacts permission
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param contactModel ContactModel whose raw contact we want to be deleted
|
|
|
|
|
+ * @return number of raw contacts deleted
|
|
|
|
|
+ */
|
|
|
|
|
+ public int deleteThreemaRawContact(@NonNull ContactModel contactModel) {
|
|
|
|
|
+ if (!ConfigUtils.isPermissionGranted(ThreemaApplication.getAppContext(), Manifest.permission.WRITE_CONTACTS)) {
|
|
|
|
|
+ return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ Account account = this.getAccount();
|
|
|
|
|
+ if (account == null) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Uri rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
|
|
|
|
+ .buildUpon()
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.SYNC1, contactModel.getIdentity())
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build();
|
|
|
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ return contentResolver.delete(rawContactUri, null, null);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("Exception", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Check if a raw contact exists where the given identity matches the entry in the contact's SYNC1 column
|
|
|
|
|
- * @param identity Threema identity to look for
|
|
|
|
|
- * @return LOOKUP_KEY of the matching Raw Contact or null if none is found
|
|
|
|
|
|
|
+ * Delete all raw contacts specified in rawContacts Map
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param rawContacts HashMap of the rawContacts to delete. The key of the map entry contains the identity
|
|
|
|
|
+ * @return Number of raw contacts that were supposed to be deleted. Does not necessarily represent the real number of deleted raw contacts.
|
|
|
*/
|
|
*/
|
|
|
- public String getRawContactLookupKeyByIdentity(String identity) {
|
|
|
|
|
|
|
+ public int deleteThreemaRawContacts(@NonNull HashMap<String, Long> rawContacts) {
|
|
|
|
|
+ if (!ConfigUtils.isPermissionGranted(ThreemaApplication.getAppContext(), Manifest.permission.WRITE_CONTACTS)) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Account account = this.getAccount();
|
|
Account account = this.getAccount();
|
|
|
if (account == null) {
|
|
if (account == null) {
|
|
|
- //do nothing
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if(this.identityLookupCache != null) {
|
|
|
|
|
- synchronized (this.identityLookupCacheLock) {
|
|
|
|
|
- String res = this.identityLookupCache.get(identity);
|
|
|
|
|
- if(res != null) {
|
|
|
|
|
- return res;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (rawContacts.isEmpty()) {
|
|
|
|
|
+ return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- String lookupKey = null;
|
|
|
|
|
|
|
+ ArrayList<ContentProviderOperation> contentProviderOperations = new ArrayList<>();
|
|
|
|
|
|
|
|
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
|
|
|
|
|
- ContextCompat.checkSelfPermission(ThreemaApplication.getAppContext(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
- //get linked contact!
|
|
|
|
|
- Cursor c1 = null;
|
|
|
|
|
- try {
|
|
|
|
|
- c1 = ThreemaApplication.getAppContext().getContentResolver().query(
|
|
|
|
|
- ContactsContract.Data.CONTENT_URI,
|
|
|
|
|
- LOOKUP_KEY_PROJECTION,
|
|
|
|
|
- ContactsContract.RawContacts.ACCOUNT_NAME + "=? and "
|
|
|
|
|
- + ContactsContract.RawContacts.ACCOUNT_TYPE + "=? and "
|
|
|
|
|
- + ContactsContract.RawContacts.SYNC1 + "=?", new String[]{
|
|
|
|
|
- String.valueOf(account.name),
|
|
|
|
|
- String.valueOf(account.type),
|
|
|
|
|
- String.valueOf(identity),
|
|
|
|
|
- },
|
|
|
|
|
- null);
|
|
|
|
|
-
|
|
|
|
|
- if (c1 != null) {
|
|
|
|
|
- if (c1.moveToFirst()) {
|
|
|
|
|
- lookupKey = c1.getString(0);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- // JollaPhone crashes within this query
|
|
|
|
|
- logger.error("Exception", e);
|
|
|
|
|
- } finally {
|
|
|
|
|
- if (c1 != null) {
|
|
|
|
|
- c1.close();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (Map.Entry<String, Long> rawContact : rawContacts.entrySet()) {
|
|
|
|
|
+ if (!TestUtil.empty(rawContact.getKey())) {
|
|
|
|
|
+ ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(
|
|
|
|
|
+ ContactsContract.RawContacts.CONTENT_URI
|
|
|
|
|
+ .buildUpon()
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.SYNC1, rawContact.getKey())
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build()
|
|
|
|
|
+ );
|
|
|
|
|
+ contentProviderOperations.add(builder.build());
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (this.identityLookupCache != null) {
|
|
|
|
|
- synchronized (this.identityLookupCacheLock) {
|
|
|
|
|
- this.identityLookupCache.put(identity, lookupKey);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ int operationCount = contentProviderOperations.size();
|
|
|
|
|
+ if (operationCount > 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ThreemaApplication.getAppContext().getContentResolver().applyBatch(
|
|
|
|
|
+ ContactsContract.AUTHORITY,
|
|
|
|
|
+ contentProviderOperations);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("Error during raw contact deletion! ", e);
|
|
|
}
|
|
}
|
|
|
|
|
+ contentProviderOperations.clear();
|
|
|
}
|
|
}
|
|
|
- return lookupKey;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ logger.debug("Deleted {} raw contacts", operationCount);
|
|
|
|
|
+
|
|
|
|
|
+ return operationCount;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Delete the raw contact where the given identity matches the entry in the contact's SYNC1 column
|
|
|
|
|
- * @param identity Threema identity to look for
|
|
|
|
|
|
|
+ * Delete all raw contacts associated with Threema (including stray ones)
|
|
|
|
|
+ * Safe to be called without permission
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return number of raw contacts deleted
|
|
|
*/
|
|
*/
|
|
|
- public void deleteRawContactByIdentity(String identity) {
|
|
|
|
|
|
|
+ public int deleteAllThreemaRawContacts() {
|
|
|
|
|
+ if (!ConfigUtils.isPermissionGranted(ThreemaApplication.getAppContext(), Manifest.permission.WRITE_CONTACTS)) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
Account account = this.getAccount();
|
|
Account account = this.getAccount();
|
|
|
if (account == null) {
|
|
if (account == null) {
|
|
|
- //do nothing
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Uri rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
|
Uri rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
|
@@ -847,20 +558,183 @@ public class AndroidContactUtil {
|
|
|
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build();
|
|
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build();
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- contentResolver.delete(rawContactUri,
|
|
|
|
|
- ContactsContract.RawContacts.SYNC1 + " = ?", new String[]{
|
|
|
|
|
- identity
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ return contentResolver.delete(rawContactUri, null, null);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("Exception", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get a list of all Threema raw contacts from the contact database. This may include "stray" contacts.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return HashMap containing identity as key and android contact id as value
|
|
|
|
|
+ */
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public HashMap<String, Long> getAllThreemaRawContacts() {
|
|
|
|
|
+ if (!ConfigUtils.isPermissionGranted(ThreemaApplication.getAppContext(), Manifest.permission.WRITE_CONTACTS)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Account account = this.getAccount();
|
|
|
|
|
+ if (account == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Uri rawContactUri = ContactsContract.RawContacts.CONTENT_URI
|
|
|
|
|
+ .buildUpon()
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
|
|
|
|
|
+ .appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type).build();
|
|
|
|
|
+
|
|
|
|
|
+ HashMap<String, Long> rawContacts = new HashMap<>();
|
|
|
|
|
+ Cursor cursor = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ cursor = contentResolver.query(rawContactUri, RAW_CONTACT_PROJECTION, null, null, null);
|
|
|
|
|
+ if (cursor != null){
|
|
|
|
|
+ while (cursor.moveToNext()) {
|
|
|
|
|
+ Long contactId = cursor.getLong(0);
|
|
|
|
|
+ String identity = cursor.getString(1);
|
|
|
|
|
+ rawContacts.put(identity, contactId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
logger.error("Exception", e);
|
|
logger.error("Exception", e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (cursor != null) {
|
|
|
|
|
+ cursor.close();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ return rawContacts;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public boolean isAndroidContactNameMaster(ContactModel contactModel) {
|
|
|
|
|
- if(contactModel != null) {
|
|
|
|
|
- return !TestUtil.empty(contactModel.getAndroidContactId());
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Get the "main" raw contact representing the Android contact specified by the lookup key
|
|
|
|
|
+ * We consider the contact referenced as display name source for the Android contact as the "main" contact
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param lookupKey The lookup key of the contact
|
|
|
|
|
+ * @return ID of the raw contact or 0 if none is found
|
|
|
|
|
+ */
|
|
|
|
|
+ @RequiresPermission(Manifest.permission.READ_CONTACTS)
|
|
|
|
|
+ public long getMainRawContact(String lookupKey) {
|
|
|
|
|
+ long rawContactId = 0;
|
|
|
|
|
+ Uri lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey);
|
|
|
|
|
+
|
|
|
|
|
+ Cursor cursor = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ cursor = ThreemaApplication.getAppContext().getContentResolver().query(
|
|
|
|
|
+ lookupUri,
|
|
|
|
|
+ new String[]{
|
|
|
|
|
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? ContactsContract.Contacts.NAME_RAW_CONTACT_ID : "name_raw_contact_id"
|
|
|
|
|
+ },
|
|
|
|
|
+ null,
|
|
|
|
|
+ null,
|
|
|
|
|
+ null);
|
|
|
|
|
+
|
|
|
|
|
+ if (cursor != null) {
|
|
|
|
|
+ if (cursor.moveToFirst()) {
|
|
|
|
|
+ rawContactId = cursor.getLong(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (cursor != null) {
|
|
|
|
|
+ cursor.close();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ return rawContactId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @RequiresPermission(allOf = {Manifest.permission.READ_CONTACTS, Manifest.permission.GET_ACCOUNTS})
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ @WorkerThread
|
|
|
|
|
+ public Drawable getAccountIcon(@NonNull ContactModel contactModel) {
|
|
|
|
|
+ final PackageManager pm = ThreemaApplication.getAppContext().getPackageManager();
|
|
|
|
|
+
|
|
|
|
|
+ Account myAccount = this.getAccount();
|
|
|
|
|
+ if (myAccount == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!contactModel.isSynchronized() || contactModel.getAndroidContactLookupKey() == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ long nameSourceRawContactId = getMainRawContact(contactModel.getAndroidContactLookupKey());
|
|
|
|
|
+ if (nameSourceRawContactId == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ AccountManager accountManager = AccountManager.get(ThreemaApplication.getAppContext());
|
|
|
|
|
+ AuthenticatorDescription[] descriptions = accountManager.getAuthenticatorTypes();
|
|
|
|
|
+
|
|
|
|
|
+ Drawable drawable = null;
|
|
|
|
|
+
|
|
|
|
|
+ Uri nameSourceRawContactUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, nameSourceRawContactId);
|
|
|
|
|
+ Cursor cursor = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ cursor = this.contentResolver.query(
|
|
|
|
|
+ nameSourceRawContactUri,
|
|
|
|
|
+ new String[]{
|
|
|
|
|
+ ContactsContract.RawContacts.ACCOUNT_TYPE
|
|
|
|
|
+ }, null, null, null);
|
|
|
|
|
+ if (cursor != null) {
|
|
|
|
|
+ if (cursor.moveToNext()) {
|
|
|
|
|
+ String accountType = cursor.getString(0);
|
|
|
|
|
+ for (AuthenticatorDescription description : descriptions) {
|
|
|
|
|
+ if (description.type.equalsIgnoreCase(accountType)) {
|
|
|
|
|
+ drawable = pm.getDrawable(description.packageName, description.iconId, null);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (cursor != null) {
|
|
|
|
|
+ cursor.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //if no icon found, display the icon of the phone or contacts app
|
|
|
|
|
+ if (drawable == null) {
|
|
|
|
|
+ for (String substitutePackageName : new String[]{
|
|
|
|
|
+ "com.android.contacts",
|
|
|
|
|
+ "com.android.providers.contacts",
|
|
|
|
|
+ "com.android.phone",
|
|
|
|
|
+ }) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ drawable = pm.getApplicationIcon(substitutePackageName);
|
|
|
|
|
+ break;
|
|
|
|
|
+ } catch (PackageManager.NameNotFoundException x) {
|
|
|
|
|
+ //
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return drawable;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Open the system's contact editor for the provided Threema contact
|
|
|
|
|
+ * @param context Context
|
|
|
|
|
+ * @param contact Threema contact
|
|
|
|
|
+ * @return true if the contact is linked with a system contact (even if no app is available for an ACTION_EDIT intent in the system), false otherwise
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean openContactEditor(Context context, ContactModel contact) {
|
|
|
|
|
+ Uri contactUri = AndroidContactUtil.getInstance().getAndroidContactUri(contact);
|
|
|
|
|
+
|
|
|
|
|
+ if (contactUri != null) {
|
|
|
|
|
+ Intent intent = new Intent(Intent.ACTION_EDIT);
|
|
|
|
|
+ intent.setDataAndType(contactUri, ContactsContract.Contacts.CONTENT_ITEM_TYPE);
|
|
|
|
|
+ intent.putExtra("finishActivityOnSaveCompleted", true);
|
|
|
|
|
+
|
|
|
|
|
+ // make sure users are coming back to threema and not the external activity
|
|
|
|
|
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
|
|
|
|
+ if (intent.resolveActivity(context.getPackageManager()) != null) {
|
|
|
|
|
+ context.startActivity(intent);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Toast.makeText(context, "No contact editor found on device.", Toast.LENGTH_SHORT).show();
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|