phoneNumberLookup(Context context, String number) {
//Log.e("PhoneNumberLookup", number);
@@ -122,5 +139,422 @@
}
}
+
+ /**
+ * Return all the NAME_RAW_CONTACT_IDS which contribute an entry to a Contact in the database
+ *
+ * If the user has, for example, joined several contacts, on the phone, the IDs returned will
+ * be representative of the joined contact
+ *
+ * See here: https://developer.android.com/reference/android/provider/ContactsContract.Contacts.html
+ * for more information about the connection between contacts and raw contacts
+ *
+ * @param context android.content.Context running the request
+ * @return List of each NAME_RAW_CONTACT_ID in the Contacts database
+ */
+ public static List getAllContactRawContactIDs(Context context) {
+ ArrayList toReturn = new ArrayList();
+
+ // Define the columns we want to read from the Contacts database
+ final String[] projection = new String[]{
+ ContactsContract.Contacts.NAME_RAW_CONTACT_ID
+ };
+
+ Uri contactsUri = ContactsContract.Contacts.CONTENT_URI;
+ Cursor contactsCursor = context.getContentResolver().query(
+ contactsUri,
+ projection,
+ null, null, null);
+ if (contactsCursor != null && contactsCursor.moveToFirst()) {
+ do {
+ Long contactID;
+
+ int idIndex = contactsCursor.getColumnIndex(ContactsContract.Contacts.NAME_RAW_CONTACT_ID);
+ if (idIndex != -1) {
+ contactID = contactsCursor.getLong(idIndex);
+ } else {
+ // Something went wrong with this contact
+ // If you are experiencing this, please open a bug report indicating how you got here
+ Log.e("ContactsHelper", "Got a contact which does not have a NAME_RAW_CONTACT_ID");
+ continue;
+ }
+
+ toReturn.add(contactID);
+ } while (contactsCursor.moveToNext());
+ try {
+ contactsCursor.close();
+ } catch (Exception e) {
+ }
+ }
+
+ return toReturn;
+ }
+
+ /**
+ * Get VCards using the batch database query which requires Android API 21
+ *
+ * @param context android.content.Context running the request
+ * @param IDs collection of raw contact IDs to look up
+ * @param lookupKeys
+ * @return Mapping of raw contact IDs to corresponding VCard
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ protected static Map getVCardsFast(Context context, Collection IDs, Map lookupKeys) {
+ Map toReturn = new HashMap<>();
+ StringBuilder keys = new StringBuilder();
+
+ List orderedIDs = new ArrayList<>(IDs);
+
+ for (Long ID : orderedIDs) {
+ String key = lookupKeys.get(ID);
+ keys.append(key);
+ keys.append(':');
+ }
+
+ // Remove trailing ':'
+ keys.deleteCharAt(keys.length() - 1);
+
+ Uri vcardURI = Uri.withAppendedPath(
+ ContactsContract.Contacts.CONTENT_MULTI_VCARD_URI,
+ Uri.encode(keys.toString()));
+
+ InputStream input;
+ StringBuilder vcardJumble = new StringBuilder();
+ try {
+ input = context.getContentResolver().openInputStream(vcardURI);
+
+ BufferedReader bufferedInput = new BufferedReader(new InputStreamReader(input));
+ String line;
+
+ while ((line = bufferedInput.readLine()) != null) {
+ vcardJumble.append(line).append('\n');
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ // WARNING -- UNDOCUMENTED BEHAVIOR USED AHEAD
+ // Android returns the vcards as a big flat string of vcards
+ // It **appears** that these vcards are in the same order as we requested them
+ // We are relying on this behavior to connect VCards to the unique IDs
+
+ String[] vcards = vcardJumble.toString().split("END:VCARD");
+ for (int index = 0; index < orderedIDs.size(); index ++) {
+ String vcard = vcards[index] + "END:VCARD";
+ Long ID = orderedIDs.get(index);
+ toReturn.put(ID, vcard);
+ }
+
+ return toReturn;
+ }
+
+ /**
+ * Get VCards using serial database lookups. This is tragically slow, but at least supports old Android versions
+ *
+ * Use getVCardsFast for API >= 21
+ *
+ * @param context android.content.Context running the request
+ * @param IDs collection of raw contact IDs to look up
+ * @param lookupKeys
+ * @return
+ */
+ protected static Map getVCardsSlow(Context context, Collection IDs, Map lookupKeys) {
+ Map toReturn = new HashMap<>();
+
+ for ( Long ID : lookupKeys.keySet() ) {
+ String lookupKey = lookupKeys.get(ID);
+ Uri vcardURI = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey);
+ InputStream input;
+ try {
+ input = context.getContentResolver().openInputStream(vcardURI);
+
+ BufferedReader bufferedInput = new BufferedReader(new InputStreamReader(input));
+
+ StringBuilder vcard = new StringBuilder();
+ String line;
+ while ((line = bufferedInput.readLine()) != null) {
+ vcard.append(line).append('\n');
+ }
+
+ toReturn.put(ID, vcard.toString());
+ } catch (FileNotFoundException e) {
+ // If you are experiencing this, please open a bug report indicating how you got here
+ e.printStackTrace();
+ continue;
+ } catch (IOException e) {
+ e.printStackTrace();
+ continue;
+ }
+ }
+
+ return toReturn;
+ }
+
+ /**
+ * Get the VCard for every specified raw contact ID
+ *
+ * @param context android.content.Context running the request
+ * @param IDs collection of raw contact IDs to look up
+ * @return Mapping of raw contact IDs to the corresponding VCard
+ */
+ public static Map getVCardsForContactIDs(Context context, Collection IDs) {
+ Map toReturn = new HashMap<>();
+
+ // Get the contacts' lookup keys, since that is how VCard is looked up
+ final String[] contactsProjection = new String[]{
+ ContactsContract.Contacts.LOOKUP_KEY
+ };
+
+ Map> lookupKeysMap = getColumnsFromContactsForRawContactIDs(context, IDs, contactsProjection);
+ Map lookupKeys = new HashMap<>();
+
+ for (Long ID : lookupKeysMap.keySet()) {
+ Map returnedColumns = lookupKeysMap.get(ID);
+ lookupKeys.put(ID, (String) returnedColumns.get(ContactsContract.Contacts.LOOKUP_KEY));
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return getVCardsFast(context, IDs, lookupKeys);
+ } else {
+ return getVCardsSlow(context, IDs, lookupKeys);
+ }
+
+ }
+
+ /**
+ * Return a mapping of raw contact IDs to a map of the requested data from the Contacts database
+ *
+ * If for some reason there is no row associated with the raw contact ID in the database,
+ * there will not be a corresponding field in the returned map
+ *
+ * @param context android.content.Context running the request
+ * @param IDs collection of raw contact IDs to look up
+ * @param contactsProjection List of column names to extract, defined in ContactsContract.Contacts
+ * @return mapping of raw contact IDs to desired values, which are a mapping of column names to the data contained there
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB) // Needed for Cursor.getType(..)
+ public static Map> getColumnsFromContactsForRawContactIDs(Context context, Collection IDs, String[] contactsProjection) {
+ HashMap> toReturn = new HashMap<>();
+
+ // Define the columns we want to read from the RawContacts database
+ final String[] rawContactsProjection = new String[]{
+ ContactsContract.RawContacts._ID,
+ ContactsContract.RawContacts.CONTACT_ID
+ };
+
+ Uri rawContactsUri = ContactsContract.RawContacts.CONTENT_URI;
+ Uri contactsUri = ContactsContract.Contacts.CONTENT_URI;
+
+ Cursor rawContactsCursor = context.getContentResolver().query(
+ rawContactsUri,
+ rawContactsProjection,
+ null,
+ null,
+ null);
+
+ if (rawContactsCursor != null && rawContactsCursor.moveToFirst()) {
+ do {
+ Long rawContactID;
+ Long contactID;
+
+ int rawContactIDIndex = rawContactsCursor.getColumnIndex(ContactsContract.RawContacts._ID);
+ if (rawContactIDIndex != -1) {
+ rawContactID = rawContactsCursor.getLong(rawContactIDIndex);
+ } else {
+ // This raw contact didn't have an ID? Something is very wrong.
+ // If you are experiencing this, please open a bug report indicating how you got here
+ Log.e("ContactsHelper", "Got a raw contact which does not have an _ID");
+ continue;
+ }
+
+ // Filter only for the rawContactIDs we were asked to look up
+ if (!IDs.contains(rawContactID)) {
+ // This should be achievable (and faster) by providing a selection
+ // and selectionArgs when fetching rawContactsCursor, but I can't
+ // figure that out
+ continue;
+ }
+
+ int contactIDIndex = rawContactsCursor.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID);
+ if (contactIDIndex != -1) {
+ contactID = rawContactsCursor.getLong(contactIDIndex);
+ } else {
+ // Something went wrong with this contact
+ // If you are experiencing this, please open a bug report indicating how you got here
+ Log.e("ContactsHelper", "Got a raw contact which does not have a CONTACT_ID");
+ continue;
+ }
+
+ // Filter on only the contact we are interested in
+ final String contactsSelection = ContactsContract.Contacts._ID + " == ? ";
+ final String[] contactsArgs = new String[]{contactID.toString()};
+
+ Cursor contactsCursor = context.getContentResolver().query(
+ contactsUri,
+ contactsProjection,
+ contactsSelection,
+ contactsArgs, null
+ );
+
+ Map requestedData = new HashMap<>();
+
+ if (contactsCursor != null && contactsCursor.moveToFirst()) {
+ // For each column, collect the data from that column
+ for (String column : contactsProjection) {
+ int index = contactsCursor.getColumnIndex(column);
+ // Since we might be getting various kinds of data, Object is the best we can do
+ Object data;
+ int type;
+ if (index == -1) {
+ // This raw contact didn't have an ID? Something is very wrong.
+ // If you are experiencing this, please open a bug report indicating how you got here
+ Log.e("ContactsHelper", "Got a raw contact which does not have an _ID");
+ continue;
+ }
+
+ type = contactsCursor.getType(index);
+ switch (type) {
+ case Cursor.FIELD_TYPE_INTEGER:
+ data = contactsCursor.getInt(index);
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ data = contactsCursor.getFloat(index);
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ data = contactsCursor.getString(index);
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ data = contactsCursor.getBlob(index);
+ break;
+ default:
+ Log.e("ContactsHelper", "Got an undefined type of column " + column);
+ continue;
+ }
+
+ requestedData.put(column, data);
+ }
+ }
+ contactsCursor.close();
+
+ toReturn.put(rawContactID, requestedData);
+ } while (rawContactsCursor.moveToNext());
+ rawContactsCursor.close();
+ }
+
+ return toReturn;
+ }
+
+ /**
+ * Return a mapping of raw contact IDs to a map of the requested data from the Data database
+ *
+ * If for some reason there is no row associated with the raw contact ID in the database,
+ * there will not be a corresponding field in the returned map
+ *
+ * For some types of data, there may be many entries in the Data database with the same raw contact ID,
+ * so a list of the relevant data is returned
+ *
+ * @param context android.content.Context running the request
+ * @param IDs collection of raw contact IDs to look up
+ * @param dataMimetype Mimetype of the column to look up, defined in ContactsContract.CommonDataKinds..CONTENT_ITEM_TYPE
+ * @param dataProjection List of column names to extract, defined in ContactsContract.CommonDataKinds.
+ * @return mapping of raw contact IDs to desired values, which are a mapping of column names to the data contained there
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB) // Needed for Cursor.getType(..)
+ public static Map>> getColumnsFromDataForRawContactIDs(Context context, Collection IDs, String dataMimetype, String[] dataProjection) {
+ HashMap>> toReturn = new HashMap<>();
+
+ // Define a filter for the type of data we were asked to get
+ final String dataSelection = ContactsContract.Data.MIMETYPE + " == ?";
+ final String[] dataSelectionArgs = {dataMimetype};
+
+ Uri dataUri = ContactsContract.Data.CONTENT_URI;
+
+ // Regardless of what the user requested, we need the RAW_CONTACT_ID field
+ // This will not be returned to the user if it wasn't asked for
+ Set actualDataProjectionSet = new HashSet();
+ actualDataProjectionSet.addAll(Arrays.asList(dataProjection));
+ actualDataProjectionSet.add(ContactsContract.Data.RAW_CONTACT_ID);
+
+ String[] actualDataProjection = new String[0];
+ actualDataProjection = actualDataProjectionSet.toArray(actualDataProjection);
+
+ Cursor dataCursor = context.getContentResolver().query(
+ dataUri,
+ actualDataProjection,
+ dataSelection,
+ dataSelectionArgs,
+ null);
+
+ if (dataCursor != null && dataCursor.moveToFirst()) {
+ do {
+ Long rawContactID;
+
+ Map requestedData = new HashMap<>();
+
+ int rawContactIDIndex = dataCursor.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID);
+ if (rawContactIDIndex != -1) {
+ rawContactID = dataCursor.getLong(rawContactIDIndex);
+ } else {
+ // This didn't have a RAW_CONTACT_ID? Something is very wrong.
+ // If you are experiencing this, please open a bug report indicating how you got here
+ Log.e("ContactsHelper", "Got a data contact which does not have a RAW_CONTACT_ID");
+ continue;
+ }
+
+ // Filter only for the rawContactIDs we were asked to look up
+ if (!IDs.contains(rawContactID)) {
+ // This should be achievable (and faster) by providing a selection
+ // and selectionArgs when fetching dataCursor, but I can't
+ // figure that out
+ continue;
+ }
+ // For each column, collect the data from that column
+ for (String column : dataProjection) {
+ int index = dataCursor.getColumnIndex(column);
+ // Since we might be getting various kinds of data, Object is the best we can do
+ Object data;
+ int type;
+ if (index == -1) {
+ // This raw contact didn't have an ID? Something is very wrong.
+ // If you are experiencing this, please open a bug report indicating how you got here
+ Log.e("ContactsHelper", "Got a raw contact which does not have an _ID");
+ continue;
+ }
+
+ type = dataCursor.getType(index);
+ switch (type) {
+ case Cursor.FIELD_TYPE_INTEGER:
+ data = dataCursor.getInt(index);
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ data = dataCursor.getFloat(index);
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ data = dataCursor.getString(index);
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ data = dataCursor.getBlob(index);
+ break;
+ default:
+ Log.w("ContactsHelper", "Got an undefined type of column " + column + " -- Skipping");
+ continue;
+ }
+
+ requestedData.put(column, data);
+ }
+
+ // If we have not already stored some data for this contact, make a new list
+ if (!toReturn.containsKey(rawContactID)) {
+ toReturn.put(rawContactID, new ArrayList