For a Contacts backup app, I save all the information to a CSV file, and then I need to restore it back. It works great, however if I press restore twice, it duplicates all the contacts.
I tried the following code to remove duplicates, it does work but fails in certain cases.
Basically it fails when there is no explicit DISPLAY_NAME, for e.g. if a contact seems to only have a phone number and the DISPLAY_NAME is the phone number, or same for an email address. I cannot understand why it wont always work since it does seem that the DISPLAY_NAME field contains phonenumber/email address.
Here is the code that I used:
private boolean contactExists(String displayname, String emailstring, String phonestring){
Cursor crsr = BA.applicationContext.getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI,
new String[] { "display_name", "_id"},
"display_name = ? ",
new String[] {displayname},
null);
while (crsr.moveToNext()){
HashMap m = new HashMap();
for (int col = 0; col < crsr.getColumnCount(); col++) {
m.put(crsr.getColumnName(col), Integer.valueOf(col));
}
int id = crsr.getInt(((Integer)m.get("_id")).intValue());
String emails = GetEmails(id);
String phones = GetPhones(id);
if (emails.contentEquals(emailstring) && phones.contentEquals(phonestring))
{
crsr.close();
return true;
}
}
crsr.close();
return false;
}
UPDATE:
I tried with DISPLAY_NAME_PRIMARY with the same results.
However what I noticed is that, if I create the contacts on the same device/emulator, the duplicate is detected, when I re-restore the same contacts.
On going across devices, it seems that one reason it does not work is that at some point the special characthers are removed.
For e.g. the display name "John.Doe" is read from the CSV, but when it gets inserted, it becomes "John Doe". I cannot see where in the code the "." is ever stripped out.
What happens depends on the version of Android the device is running. If the version is Honeycomb (3.0) or later, the contact will always have a name. The name field is DISPLAY_NAME_PRIMARY, and if there's no name in any of the raw contacts, this field is set to a phone number or email address.
It's hard to know exactly what's going on with your code, because I can't tell how you're calling contactExists in all cases. But my guess is that you're looking at DISPLAY_NAME, when you may want to look at DISPLAY_NAME_PRIMARY.
As a side comment, what you're trying to do here is fraught with peril. The contacts provider is a complex system, and backing it up to a CSV may cause a lot of problems down the road. A much better strategy is to run a sync between the contacts provider and the cloud-based Google Contacts app.
Here is the code which finds duplicate contact. You need to pass the "NAME" as string and it will look for duplicate. It works in ICS but didn't check in GB, so basically you need to try your luck.
/**
* #param name
* #param context
* #return
*/
public boolean isContactExist(String name) {
boolean result = false;
try {
ContentResolver contentResolver = getContentResolver();
Uri uri = Data.CONTENT_URI;
String[] projection = new String[] { PhoneLookup._ID,
PhoneLookup.LOOKUP_KEY };
String selection = StructuredName.DISPLAY_NAME + " = ?";
String[] selectionArguments = { name };
Cursor cursor = contentResolver.query(uri, projection, selection,
selectionArguments, null);
if (cursor != null) {
while (cursor.moveToNext()) {
/*
* Log.i(TAG, "KEY = " + cursor.getString(cursor
* .getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)));
*/
result = true;
}
}
cursor.close();
} catch (Exception e) {
result = false;
e.printStackTrace();
}
return result;
}
Related
I am building an app that enables users to save and manage a list of people, with important dates such as birthdays, anniversaries, Etc. for each person.
The goal, is to send them a personalized PDF image (with their name and details), on the important day.
The problem I am having, is what to do if the user saves a person on my app, that isn't in their contact list.
In such a case, I can't send him a whatsapp message.
So, I want to add the phone number to the phone's contacts list, only if the phone number isn't already saved in the user's regular contacts list.
How do I check if phone number XXX-XXX-XXXX exists in user's contact list or not ?
String res = null;
try {
ContentResolver resolver = ctx.getContentResolver();
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
Cursor c = resolver.query(uri, new String[]{PhoneLookup.DISPLAY_NAME}, null, null, null);
if (c != null) { // cursor not null means number is found contactsTable
if (c.moveToFirst()) { // so now find the contact Name
res = c.getString(c.getColumnIndex(CommonDataKinds.Phone.DISPLAY_NAME));
}
c.close();
}
} catch (Exception ex) {
/* Ignore */
}
return res;
I am very new to app development. I am trying to read contact info without having to request permission to contacts (so I am using intents).
I get a URI with the following code in my main activity:
Intent selectContactIntent = new Intent(Intent.ACTION_PICK);
selectContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
if (selectContactIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(selectContactIntent, REQUEST_CODE_SELECT_CONTACT);
}
else {
showContactRequiredMessage(view);
}
In another (sub)activity, I do the following:
contactUri = intent.getParcelableExtra(MainActivity.CONTACT_URI);
String[] projection = new String[] {
ContactsContract.Contacts.Data._ID,
ContactsContract.Contacts.Data.MIMETYPE,
ContactsContract.Contacts.Data.DATA1,
ContactsContract.Contacts.Data.DATA2,
ContactsContract.Contacts.Data.DATA3,
ContactsContract.Contacts.Data.DATA4,
ContactsContract.Contacts.Data.DATA5,
ContactsContract.Contacts.Data.DATA6,
ContactsContract.Contacts.Data.DATA7,
ContactsContract.Contacts.Data.DATA8,
ContactsContract.Contacts.Data.DATA9,
ContactsContract.Contacts.Data.DATA10,
ContactsContract.Contacts.Data.DATA11,
ContactsContract.Contacts.Data.DATA12,
ContactsContract.Contacts.Data.DATA13,
ContactsContract.Contacts.Data.DATA14,
ContactsContract.Contacts.Data.DATA15
};
Cursor contactResults = getContentResolver().query(contactUri, projection, null, null, null);
The last line throws the exception java.lang.IllegalArgumentException: Invalid column <any column after _ID>
My app doesn't require all of the data in reality I just want to see what is available, I will most likely need first name, last name, phone, and email.
My issue is the MIME type that I set on the intent when I request the contact info. The documentation states ContactsContract.Contacts.CONTENT_TYPE should be used. However, if I use, something like ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE, I can get display name and phone number. I am not sure if this means I will need to make multiple queries to get everything (the information shown in the contact picker changes when changing the type requested).
TL;DR: Used the "wrong" content type when creating the intent to select a contact.
As mentioned in my comment to your answer, you should be able to get the information expected without using a specific CONTENT_TYPE like CommonDataKinds.Phone.CONTENT_TYPE.
The problem I see in your code is that you're trying to access Data table info from a Contacts table uri.
The ContactsContract api stored info on 3 main tables: Contacts, RawContacts and Data.
You were given a contactUri which points to an entry in the Contacts table, use the following code to read Data entries related to that contact:
long contactId = ContentUris.parseId(contactUri);
String projection = String[] { Data.MIMETYPE, Data.DISPLAY_NAME, Data.DATA1 };
String selection = Data.CONTACT_ID + " = " + contactId;
Cursor cursor = getContentResolver().query(Data.CONTENT_URI, projection, selection, null, null);
while (cursor != null && cursor.moveToNext()) {
String mime = cursor.getString(0);
String name = cursor.getString(1);
String info = cursor.getString(2);
if (mime.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
Log.d(TAG, name + ": email = " + info;
}
if (mime.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
Log.d(TAG, name + ": phone = " + info;
}
// Add more mimetypes here if needed...
}
if (cursor != null) {
cursor.close();
}
On top of contacts id, Android also got LOOK_UP key. Since id of contact can change, you can obtain user uri, using LOOK_UP key.
public static Uri lookupContactUri(String lookup, Context context){
ContentResolver contentResolver = context.getContentResolver();
Uri lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookup);
return ContactsContract.Contacts.lookupContact(contentResolver, lookupUri);
}
But how does it work? The source code of the Contacts.lookupContact doesn't tell much about the actual implementation. So can anyone explain how does they manage to pull this up?
/**
* Computes a content URI (see {#link #CONTENT_URI}) given a lookup URI.
* <p>
* Returns null if the contact cannot be found.
*/
public static Uri lookupContact(ContentResolver resolver, Uri lookupUri) {
if (lookupUri == null) {
return null;
}
Cursor c = resolver.query(lookupUri, new String[]{Contacts._ID}, null, null, null);
if (c == null) {
return null;
}
try {
if (c.moveToFirst()) {
long contactId = c.getLong(0);
return ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
}
} finally {
c.close();
}
return null;
}
Another thing I tested, is merging two contacts using ContactsContract.AggregationExceptions and then quarrying for contact uri. Both of the LOOK_UP keys yield with the same contact uri as expected.
So how are they doing it?
Since contact ids can change from time to time (e.g. when contacts sync is corrupted and contacts needs to be resynced from server), Android introduced the concept of LookupKeys and LookupUris.
A LookupKey is an opaque value, that internally can be translated by the Contacts framework to a set of fields: contact-id, raw-contact-ids, primary-display-names, etc.
Whenever you try to access a contact via a LookupUri, the system, extracts the LookupKey from the Uri, tries to access the contact-id, and compares the other fields (raw-ids, names, etc.) to the found contact, if it seems the right contact, it returns it.
If the contact-id wasn't found, or the system detects it's the wrong contact, a query is made over all contacts to find the right one (using the auxiliary fields stored on that key).
So the LookupKey acts as a quick method to either return the contact-id, or search for it in case something bad happened.
Lets say that i have 100 calls in my call log. I want to find the unique contacts (not numbers) that they have been called.
The problem is that if a contact has two phone numbers (e.g. for Contact A i have a number for home and another for mobile) i will count that contact twice!
I tried the following.
I am reading the call log. Then for each number of the call log i call the following custom function:
private String getContactID (String number)
{
String contactID = "";
ContentResolver context = getContentResolver();
/// number is the phone number
Uri lookupUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,Uri.encode(number));
String[] mPhoneNumberProjection = { PhoneLookup._ID };
Cursor cur = context.query(lookupUri,mPhoneNumberProjection, null, null, null);
try
{
if (cur.moveToFirst())
{
contactID = cur.getString(0);
return contactID;
}
}
finally
{
if (cur != null)
cur.close();
}
return contactID;
}
So then i have a calllog with contact ids and timestamp of the call and using a Set i get the unique...
The above code works fine BUT the performance if very poor! I tried it in a new Google Nexus 4 and it takes about 1600 msec! I don't want to think about older smart phones...
Any suggestions?
Use a background thread to lazy load the information in a ListView.
Initially fetch only about 10 results and display them in the list. That should happen fast. After that, in the background thread, keep on fetching information, 10 at a time, and keep on adding them to your list.
I'm stuck with the call_log functionality in Froyo. As many of you know, Froyo logs in call log not only calls but also each outgoing and incomming SMS message. You can chose in options to show all that crap, or only specific types (outgoing calls, incoming calls, sent messages, received messages etc), but since this is radio button, you cannot specify for example only ongoing and incoming calls. Very known and annoing Froyo functionality.
So I started to write some simple tool to read the call log by myself. Here is the code snippet:
try {
mCur = getApplicationContext().getContentResolver()
.query(CallLog.Calls.CONTENT_URI, columns, null, null, null );
mCur.moveToFirst();
io = mCur.getColumnIndex(CallLog.Calls._ID);
bo = mCur.getColumnIndex(CallLog.Calls.NUMBER);
no = mCur.getColumnIndex(CallLog.Calls.CACHED_NAME);
to = mCur.getColumnIndex(CallLog.Calls.TYPE);
while (mCur.isAfterLast() == false) {
i = mCur.getString(io);
b = mCur.getString(bo);
n = mCur.getString(no);
t = mCur.getString(to);
Log.i(TAG, "CallLog: ID="+i+" number="+b+" name="+n+" type="+t);
mCur.moveToNext();
}
} catch (Exception e) {
Log.e(TAG, "updateCallLog", e);
} finally {
if (mCur != null) {
mCur.close();
mCur = null;
}
}
Surprise, surprise, the call_log provider skips the sms records from the call log. So with the code above I see only call records (incoming or outgoing), all other records are skipped. The little more digging into it revealed that the CallLog provider adds internally filtering to the call log database:
02-03 09:26:42.348 E/CLCService(28244): android.database.sqlite.SQLiteException:
near ")": syntax error: , while compiling:
SELECT _id, name, number, type FROM logs WHERE (logtype=100 OR logtype=500) AND (_ID=)
Do not look for the syntax error, it was created on purpose to force provider to dump the SQL query by calling query(CallLog.Calls.CONTENT_URI, columns, "_ID=", null, null )). The (_ID=) is what is provided in the query, the rest of (logtype=100 OR logtype=500) is apparently added by the call log provider itself.
So I have two questions:
Where I can find in the Android code how the provider is adding the logtype filter? I was looking into CallLog.java and CallLogProvider.java and cannot find it.
How can I read all records from the call log in Froyo? I cannot bypass the call log provider and use my own SQL helper for this until I will not root the phone, which is not an option. Is there any other way to do it?
I'm not certain just what is going wrong but reading the call log to get just incoming or outgoing calls is simple enough. The sample below adds restrictions to the query so that it only returns data for outgoing calls made after a certain date. The where string uses question marks to indicate where the values from the wargs array should be substituted in to form the sql query.
About where the extra WHERE clause occurs. Almost certainly in the calllog provider implementation. The providers commonly have a switch statement that uses the uri that you use to open the provider and then adds restrictions based on the uri. The calllog one seems to be in packages/providers/ContactsProvider.
public static int getMinutesUsedSince(Context context, Date date) {
Uri uri = CallLog.Calls.CONTENT_URI;
String columns[] = new String[] { CallLog.Calls.DURATION };
String where = CallLog.Calls.TYPE + "=? AND " + CallLog.Calls.DATE + ">?";
String wargs[] = new String[] {
String.valueOf(CallLog.Calls.OUTGOING_TYPE),
String.valueOf(date.getTime())
};
String sortOrder = "date DESC";
Cursor c = context.getContentResolver().query(uri, columns, where, wargs, sortOrder);
long sum = 0;
int durationIndex = c.getColumnIndex(CallLog.Calls.DURATION);
if (c.moveToFirst()) {
do {
/* for each individual call, round up to the nearest minute */
long duration = c.getLong(durationIndex);
long minutes = (long)Math.ceil(duration / 60.0);
sum += minutes;
} while (c.moveToNext());
}
c.close();
return (int)sum;
}