The following code should return the last phone number in the call log.
It mostly works, but sometimes it returns not the last phone number, but the second last. If I check the list of calls, I can clearly see that the number returned is not the last in the history. Running the program again will return the last ("correct") number.
Can anyone tell what's wrong?
private String getPhoneNumber() {
String[] projection = {CallLog.Calls.NUMBER};
Cursor callLog = getContentResolver().query(
CallLog.Calls.CONTENT_URI,
projection,
null,
null,
CallLog.Calls.DEFAULT_SORT_ORDER
);
// Set number
callLog.moveToFirst();
return callLog.getString(callLog.getColumnIndex(CallLog.Calls.NUMBER));
}
Related
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;
}
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 trying to get a random contact from a phone's contact list, but without noticeably slowing down the phone. That means that I can't just grab all the contacts and stick them into an array and pick a random one from that array. I'd like to be able to get a random contact without having to get all of the contacts first.
Is this possible, and if so, how would I go about doing it?
Updated to use non-deprecated code. Query based on this answer: How to read contacts on Android 2.0
Cursor managedCursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,null, null, null, null);
Then it's just a matter of getting the size of the Cursor:
int size = managedCursor.getCount();
get a random one, read it and check if it has phone numbers. If not, choose another one:
boolean found = false;
Random rnd = new Random();
while(!found) {
int index = rnd.nextInt(size);
managedCursor.moveToPosition(index);
String name = managedCursor.getString(people.getColumnIndex(PhoneLookup.DISPLAY_NAME));
found = Boolean.parseBoolean(managedCursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)));
if (found) {
Cursor phones = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId, null, null);
while (phones.moveToNext()) {
String phoneNumber = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d("Phone found:", phoneNumber);
}
phones.close();
}
}
I don't see how you could pick a random one otherwise. And this should not slow the phone down unless it's a really large contacts list.
Now it checks for the presence of phone numbers and reads all of them if found. If not, it chooses another entry.
I am working on a method that queries a content provider using a cursor. After I delete a record, it calls the loadfromProvider method and refreshes the arraylist. The content provider normally has records in it however, when I delete all the records and the query runs automatically it throws exceptions. Here is my method:
private void loadFromProvider() {
// Clear the existing array list
EQlist.clear();
ContentResolver cr = getContentResolver();
// Return all the saved records
Cursor c = cr.query(EQProvider.CONTENT_URI, null, null, null, null);
if (c.moveToFirst()) {
do {
String details = c.getString(EQProvider.DETAILS_COLUMN);
String linkString = c.getString(EQProvider.LINK_COLUMN);
EQli q = new EQli(details, linkString);
addEQToArray(q);
} while(c.moveToNext());
}
c.close();
}
When I run this with no records in the content provider it throws the following:
java.lang.IndexOutOfBoundsException: Invalid index 1, size is 1
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:257)
I think this is due to the cursor trying to parse a null value. I am trying to figure out a way that if the cursor does not come back with any records, it bypasses the rest of the code and does nothing happens.
Any help would be appreciated. Thanks
If there are no records your cursor should be empty. If the cursor is empty then c.moveToFirst() will return false and you are not going to enter the do-while loop.
Debug your application and make sure that the cursor is empty.
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;
}