Im trying to make some changes to an app that i've downloaded, the app uses ContactContracts to show the list of contacts stored in the phone,
what im trying to do is using a content provider to show a list of contacts that i've stored in my database
the original app uses this method to load contacts from the phone and store the in "contact" object:
public void loadContactsData(){
if(SmsSchedulerApplication.contactsList.size()==0){
System.currentTimeMillis();
String[] projection = new String[] {Groups._ID};
Uri groupsUri = ContactsContract.Groups.CONTENT_URI;
groupCursor = managedQuery(groupsUri, projection, null, null, null);
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if(cursor.moveToFirst()){
do{
if(!(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)).equals("0"))){
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
Cursor phones = cr.query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = " + id, null, null);
if(phones.moveToFirst()){
Contact contact = new Contact();
contact.content_uri_id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
contact.name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
contact.number = phones.getString(phones.getColumnIndex(Phone.NUMBER));
Cursor cur = cr.query(ContactsContract.Data.CONTENT_URI, new String[]{ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID}, ContactsContract.CommonDataKinds.GroupMembership.CONTACT_ID + "=" + contact.content_uri_id, null, null);
if(cur.moveToFirst()){
do{
if(!String.valueOf(cur.getLong(cur.getColumnIndex(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID))).equals(contact.number) && cur.getLong(cur.getColumnIndex(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID))!=0){
boolean isValid = false;
if(groupCursor.moveToFirst()){
do{
if(!cur.isClosed() && !groupCursor.isClosed() && cur.getLong(cur.getColumnIndex(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)) == groupCursor.getLong(groupCursor.getColumnIndex(Groups._ID))){
isValid = true;
break;
}
}while(groupCursor.moveToNext());
}
if(isValid){
contact.groupRowId.add(cur.getLong(cur.getColumnIndex(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)));
}
}
}while(cur.moveToNext());
}
cur.close();
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(contact.content_uri_id));
InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(cr, uri);
try{
contact.image = BitmapFactory.decodeStream(input);
contact.image.getHeight();
} catch (NullPointerException e){
contact.image = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.no_image_thumbnail);
}
SmsSchedulerApplication.contactsList.add(contact);
}
}
}while(cursor.moveToNext());
}
}
}
im not familiar with using content provider so im not sure what should i define in my content provider class
You don't have to use a content provider if you don't want to. Also using a contacts provider example to realize an own user database management is the wrong approach here. The provider was evolved multiple times to support all kind of use cases. I mean, just look at the documentation of it. All this tables and and micromanagement. I doubt that you need all of this.
I suggest you do the following. First create you database scheme. Find out what tables do you need and what columns are required for the application to work. Then put it into an sqlite database and define the queries to store and retrieve the contents. This should be all necessary to make the app work.
Then if everything works and you still have the motivation or requirements, create a content provider by using the existing database as the data store withing the provider and declaring URI's to access and modify the content. Of course you then have to rewrite some portions of the existing code. But if you were using the query, update, delete and insert methods of the database, it should be no problem.
Or you do it the hard way and insert your contacts in the contacts provider by inserting raw contacts. But it you don't have to share your contacts with the system (to crate custom actions on existing contacts for example) or other apps, then just don't do it.
Uri contactUri1 = Uri.withAppendedPath(
ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number1));
Cursor phones = getContentResolver()
.query(contactUri1, PEOPLE_PROJECTION, null, null, null);
This works. I can get what I need in the cursor given number1.
What I want is to query for multiple numbers and get all the results in the same cursor.
EXAMPLE:
Uri contactUri1 = ...
Uri contactUri2 = ...
Cursor phones = getContentResolver().query(... //Both URI's
Is that possible?
I am looking to get the RAW_CONTACT_ID of a specific Contact using PhoneLookup or even just the Contacts LookupKey.
I know the contacts table has a column name_raw_contact_id that references the raw_contacts._id column but it doesn't seem to be returned when querying ContactsContract.Contacts.CONTENT_LOOKUP_URI with the contacts lookup key.
My phone lookup query is:
String[] projection = new String[] {
PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY };
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor c = resolver.query(uri, projection, null, null, null);
Then I am looking up the Contact based on the lookup key:
String[] contactProjection = new String[] {
ContactsContract.Contacts.NAME_RAW_CONTACT_ID
};
Uri contactUri = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey);
Cursor contactCursor = resolver.query(contactUri,
contactProjection, null, null, null);
However, this doesn't compile and I get
cannot find symbol: variable NAME_RAW_CONTACT_ID
location: class android.provider.ContactsContract.Contacts
But the android documentation shows NAME_RAW_CONTACT_ID as a column.
Is there any way I can get the RAW_CONTACT_ID based off either phone number or lookup key?
I found that the answer is to make a third query:
long rawContactId = -1;
Cursor c = getContentResolver().query(RawContacts.CONTENT_URI,
new String[]{RawContacts._ID},
RawContacts.CONTACT_ID + "=?",
new String[]{String.valueOf(contactId)}, null);
try {
if (c.moveToFirst()) {
rawContactId = c.getLong(0);
}
} finally {
c.close();
}
But it should be noted that there can be multiple RawContact's per one Contact and the above query will get ALL RawContact's associated with the contactId
ContactsContract.Contacts.NAME_RAW_CONTACT_ID column needs API level 21 or greater so make sure your are compiling with this version.
Also you can use ContactsContract.PhoneLookup._ID column in your first query to get the Contact_Id and then use this Contact_Id in your 3rd query so your problem will be solved in 2 queries instead of 3.
private HashSet<Long> getRawContactIdsForContact(long contactId)
{
HashSet<Long> ids = new HashSet<Long>();
Cursor cursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
new String[]{RawContacts._ID},
RawContacts.CONTACT_ID + "=?",
new String[]{String.valueOf(contactId)}, null);
if (cursor != null && cursor.moveToFirst())
{
do
{
ids.add(cursor.getLong(0));
} while (cursor.moveToNext());
cursor.close();
}
return ids;
}
Hi I am trying to get a contacts first name. I know how to get a contacts full name, but I cannot figure out how obtain just the name. any ideas?
Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
String[] mPhoneNumberProjection = { PhoneLookup._ID, PhoneLookup.NUMBER, PhoneLookup.DISPLAY_NAME };
Cursor cur = context.getContentResolver().query(lookupUri,mPhoneNumberProjection, null, null, null);
try {
if (cur.moveToFirst()) {
String name = cur.getString(2);
// do something with the name
}
} finally {
cur.close();
}
I haven't personally tried this, but you may have to do a further RawContacts.CONTENT_URI query against the PersonLookup.LOOKUP_KEY from the first query, allowing you to retrieve the StructuredName.GIVEN_NAME field.
What would be the correct way to add DISTINCT and/or GROUPBY to ContentResolver-based queries?
Right now I have to create custom URI for each special case.
Is there a better way?
(I still program for 1.5 as lowest common denominator)
You can do nice hack when querying contentResolver, use:
String selection = Models.SOMETHING + "=" + something + ") GROUP BY (" + Models.TYPE;
If you want to use DISTINCT with SELECT more then one column, You need to use GROUP BY.
Mini Hack over ContentResolver.query for use this:
Uri uri = Uri.parse("content://sms/inbox");
Cursor c = getContentResolver().query(uri,
new String[]{"DISTINCT address","body"}, //DISTINCT
"address IS NOT NULL) GROUP BY (address", //GROUP BY
null, null);
if(c.moveToFirst()){
do{
Log.v("from", "\""+c.getString(c.getColumnIndex("address"))+"\"");
Log.v("text", "\""+c.getString(c.getColumnIndex("body"))+"\"");
} while(c.moveToNext());
}
This code select one last sms for each of senders from device inbox.
Note: before GROUP BY we always need to write at least one condition.
Result SQL query string inside ContentResolver.query method will:
SELECT DISTINCT address, body FROM sms WHERE (type=1) AND (address IS NOT NULL) GROUP BY (address)
Since no one came to answer I'm just going to tell how I solved this. Basically I would create custom URI for each case and pass the criteria in selection parameter. Then inside ContentProvider#query I would identify the case and construct raw query based on table name and selection parameter.
Here's quick example:
switch (URI_MATCHER.match(uri)) {
case TYPES:
table = TYPES_TABLE;
break;
case TYPES_DISTINCT:
return db.rawQuery("SELECT DISTINCT type FROM types", null);
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return db.query(table, null, selection, selectionArgs, null, null, null);
In your overridden ContentProvider query method have a specific URI mapping to using distinct.
Then use SQLiteQueryBuilder and call the setDistinct(boolean) method.
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
boolean useDistinct = false;
switch (sUriMatcher.match(uri))
{
case YOUR_URI_DISTINCT:
useDistinct = true;
case YOUR_URI:
qb.setTables(YOUR_TABLE_NAME);
qb.setProjectionMap(sYourProjectionMap);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// If no sort order is specified use the default
String orderBy;
if (TextUtils.isEmpty(sortOrder))
{
orderBy = DEFAULT_SORT_ORDER;
}
else
{
orderBy = sortOrder;
}
// Get the database and run the query
SQLiteDatabase db = mDBHelper.getReadableDatabase();
// THIS IS THE IMPORTANT PART!
qb.setDistinct(useDistinct);
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
if (c != null)
{
// Tell the cursor what uri to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
Though I have not used Group By, I have used Distinct in content resolver query.
Cursor cursor = contentResolver
.query(YOUR_URI,
new String[] {"Distinct "+ YOUR_COLUMN_NAME},
null,
null, null);
Adding the Distinct keyword in the projection worked for me too, however, it only worked when the distinct keyword was the first argument:
String[] projection = new String[]{"DISTINCT " + DBConstants.COLUMN_UUID, ... };
In some condition, we can use "distinct(COLUMN_NAME)" as the selection,
and it work perfect.
but in some condition, it will cause a exception.
when it cause a exception, i will use a HashSet to store the column values....
// getting sender list from messages into spinner View
Spinner phoneListView = (Spinner) findViewById(R.id.phone_list);
Uri uri = Uri.parse("content://sms/inbox");
Cursor c = getContentResolver().query(uri, new String[]{"Distinct address"}, null, null, null);
List <String> list;
list= new ArrayList<String>();
list.clear();
int msgCount=c.getCount();
if(c.moveToFirst()) {
for(int ii=0; ii < msgCount; ii++) {
list.add(c.getString(c.getColumnIndexOrThrow("address")).toString());
c.moveToNext();
}
}
phoneListView.setAdapter(new ArrayAdapter<String>(BankActivity.this, android.R.layout.simple_dropdown_item_1line, list));
When you have multiple columns in your projection you should do like this:
val uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
"DISTINCT " + MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
MediaStore.Images.Media.BUCKET_ID,
MediaStore.MediaColumns.DATA
)
val groupBySelection = " 1) GROUP BY (${MediaStore.Images.Media.BUCKET_ID}"
contentResolver.query(
uri,
projection,
null,
groupBySelection,
null,
null
)
groupBySelection with closing bracket and number "1" inside is a tiny hack, but it works absolutely fine
I created a utility method for using group by and distinct.
Usage
Here is an example of selecting unseen thread_id with the last message date from the MMS database.
query(contentResolver= contentResolver,
select = arrayOf(Mms.THREAD_ID, "max(${Mms.DATE}) as date"),
from = Mms.CONTENT_URI,
where = "${Mms.SEEN} = 0",
groupBy = "1",
orderBy = "2 desc"
).use {
while (it?.moveToNext() == true){
val threadId = it.getInt(0)
val date = it.getLong(1)
}
}
Source
fun query(
contentResolver: ContentResolver,
from: Uri,
select: Array<String>,
where: String? = null,
groupBy: Array<out String>? = null,
distinct: Boolean = false,
selectionArgs: Array<out String>? = null,
orderBy: String? = null,
): Cursor? {
val tmpSelect = select[0]
val localWhere =
if (groupBy == null) where
else "${where ?: "1"}) group by (${groupBy.joinToString()}"
if (distinct) {
select[0] = "distinct $tmpSelect"
}
val query = contentResolver.query(from, select, localWhere, selectionArgs, orderBy)
select[0] = tmpSelect
return query
}
Maybe its more simple to get distinct values,
try to add the DISTINCT word before the column name you want into the projection table
String[] projection = new String[]{
BaseColumns._ID,
"DISTINCT "+ Mediastore.anything.you.want
};
and use it as an argument to query method of the content resolver!
I hope to help you, cause I have the same question before some days