I'm wondering if the following is possible:
I've an app (service) that stays in background, and gets triggered whenever the user
Adds/deletes/updates a contact
Installs/uninstalls an app
Adds/deletes/renames a file on the FS
Do you think this is possible guys? (in a proper way of course, if it's possible to do it by hacking and dirty stuff I'd pass)
I tried to look over the internet a bit but didn't find discussions related to this point.
What's your guess ?
Haven't tried any of this myself, but:
http://mylifewithandroid.blogspot.com/2008/03/observing-content.html seems to deal with detecting contact data changes. Basically you need to register a ContentObserver and handle the changes you are notified of.
Check out http://developer.android.com/reference/android/content/Intent.html - from that you can register a BroadcastReceiver to be notified of applications being installed or uninstalled. Look for ACTION_PACKAGE_ADDED and ACTION_PACKAGE_REMOVED
Please refer to How to detect file or folder changes in Android? for how to detect when files are changed in the filesystem. You may be limited to your sandbox with a FileObserver, I'm not sure. Also - a rename doesn't seem to be explicitly notified, but you will probably detect it from a MOVED_FROM followed by MOVED_TO, or possibly a DELETE followed by CREATE
Found in the SDK sample for SDK version 5+:
/**
* Retrieves the contact information.
*/
#Override
public ContactInfo loadContact(ContentResolver contentResolver, Uri contactUri) {
ContactInfo contactInfo = new ContactInfo();
long contactId = -1;
// Load the display name for the specified person
Cursor cursor = contentResolver.query(contactUri,
new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
try {
if (cursor.moveToFirst()) {
contactId = cursor.getLong(0);
contactInfo.setDisplayName(cursor.getString(1));
}
} finally {
cursor.close();
}
// Load the phone number (if any).
cursor = contentResolver.query(Phone.CONTENT_URI,
new String[]{Phone.NUMBER},
Phone.CONTACT_ID + "=" + contactId, null, Phone.IS_SUPER_PRIMARY + " DESC");
try {
if (cursor.moveToFirst()) {
contactInfo.setPhoneNumber(cursor.getString(0));
}
} finally {
cursor.close();
}
return contactInfo;
}
You can specify the contact columns you want to retreive with Cursor cursor = contentResolver.query(contactUri, new String[]{Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null); The column names are described at http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html and looking at the sample, it seems cursor.getLong(0) here is the contact ID you're looking for. It also seems that it is volatile depending on how the contact is edited and how others are added, but you're catching those too so you should be able to handle those cases.
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;
}
I want to get the count of missed calls and unread messages in my application. and I'd like to open the relevant application when user click on the count.
Now biggest problem is how to get the count?
I searched online but couldn't find any solution.
Thanks in advance.
http://developer.android.com/reference/android/provider/CallLog.Calls.html
Take a look at this CallLog class. All you need is to query the phone for any calls then extract missed one (оr do this when you are querying the phone, in the selection arguments). The same applies for the messages. SMS are stored in the Content provider under "content://sms/"
Then just get the count of rows in the Cursor that is return by the query. :)
I hope this helps.
For missed calls:
String[] projection = {
CallLog.Calls.CACHED_NAME,
CallLog.Calls.CACHED_NUMBER_LABEL,
CallLog.Calls.TYPE
};
String where = CallLog.Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
Cursor c = this.getContentResolver().query(
CallLog.Calls.CONTENT_URI,
selection,
where,
null,
null
);
c.moveToFirst();
Log.d("CALL", ""+c.getCount()); //do some other operation
if (c.getCount() == SOME_VALUE_TO_START_APP_ONE) //...etc etc
In the where clause you set condition for selection of data. In our case we need everything which type equals CallLog.Calls.MISSED_TYPE. We select project the Name of the caller and his number, ofcourse you can specify more information to be queried like type of number like mobile, home, work.
The expression is equivalent to SQL query, something like: SELECT CACHED_NAME, CACHED_NUMBER_LABEL, TYPE FROM CONTENT_URI WHERE TYPE=MISSED_TYPE
This requires permissions to be added to the Manifest
<uses-permission android:name="android.permission.READ_LOGS"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
For querying SMS ContentProvider:
Uri sms_content = Uri.parse("content://sms");
Cursor c = this.getContentResolver().query(sms_content, null,null, null, null);
c.moveToFirst();
Log.d("SMS COUNT", "" + c.getCount()); //do some other operation
// Here proceed with the what you wanted
if (c.getCount() == SOME_VALUE_TO_START_APP_ONE)//...etc etc
You can go deeper in the content tree like specifying the type of sms, like: content://sms/sent or content://sms/inbox and add projection and selection for the second argument of the query() method like, name, person, status of the message (like the Calls example).
This requires permission:
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
As I don't have enough reputation to answer #Prasad question comment about
ERROR -> getContentResolver() is undefined for the type new Runnable(){}
getContentResolver() is part of application context, so if you are using a BroadcastReceiver use context in onReceive() function like this
#Override
public void onReceive(Context context, Intent intent) {
context.getContentResolver()
}
If you are using the code above inside an Activity, then you can use
getApplicationContext().getContentResolver()
also make sure to use [Ctrl + Shift + O (O not zero)] to organize imports
Key Shortcut for Eclipse Imports
This question should be a starting point to all of us who want to manipulate contacts in Android.
First things first
As I am aware, since API level 5 the Contacts API has changed, so in order to make the application work correct I need to check what android os is on the phone and if prior 5 use one content provider or else use the newer one. The only annoyance in this case is the warnings of deprecated I get. The application is build against Android 2.3.3 but needs to work from 1.5+
1. Querying contacts
This is the easiest part to do. Usually querying means getting data like Contact name, phones, picture, email and displaying it on a listview. For instance here is how I've done it in API prior 5
String[] projectionPeople = new String[] {People._ID, People.NAME,};
String[] projectionPhone = new String[] {Phones.NUMBER};
try {
// Get the base URI for People table in Contacts content provider.
// which is: content://contacts/people/
Uri contactUri = People.CONTENT_URI;
ContentResolver resolver = getContentResolver();
Cursor phonesCursor = null;
Cursor peopleCursor = resolver.query (contactUri,
projectionPeople, //Which columns to return.
"People.NAME is not null", // WHERE clause--we won't specify.
null, // Selection Args??
People.DEFAULT_SORT_ORDER); // Order-by name
if (peopleCursor != null && peopleCursor.getCount() >0)
{
// go to the beginning of the list
peopleCursor.moveToFirst();
do
{
//do something with current contact info
phoneUri= Uri.withAppendedPath(personUri, Contacts.People.Phones.CONTENT_DIRECTORY);
phonesCursor = resolver.query(phoneUri,
projectionPhone,
null,
null,
Phones.DEFAULT_SORT_ORDER);
if (phonesCursor!=null && phonesCursor.getCount()>0)
{
phonesCursor.moveToFirst();
lstPhones = new ArrayList<String>();
do
{
//add phone numbers to a List<String> for instance
} while (phonesCursor.moveToNext());
if (phonesCursor != null && !phonesCursor.isClosed())
phonesCursor.close();
} while (peopleCursor.moveToNext());
if (peopleCursor != null && !peopleCursor.isClosed())
peopleCursor.close();
}
}
catch (Exception ex)
{
}
}
Haven't tried it yet on the new api but the cursor should be like
final String[] projection = new String[] {
RawContacts.CONTACT_ID, // the contact id column
RawContacts.DELETED // column if this contact is deleted
};
final Cursor rawContacts = managedQuery(RawContacts.CONTENT_URI, // the URI for raw contact provider
projection
null, // selection = null, retrieve all entries
null, // selection is without parameters
null); // do not order
Sure, this needs to be elaborated a bit more, but it should provide the basics of simple query against Contacts content provider
2. Backup
My first thought on this was: if I know the Id of a Contact, I create tables in a sqlite database exactly how the cursor columns are and insert all the data into my tables. This is not an easy task as it requires a lot of codding not to mention that different apis have different table structures. What would be the best solution to backup one contact or multiple contacts ?
3. Delete
This should work on all apis using content providers, but data is spread on many packages and uris and I'm not sure from where to delete
4. Insert
After a contact is backed up, I may need to restore/insert it again. As in case of deletion, on which uris do I need to insert ?
Please, let's try to elaborate this issues so in the futures, who needs to use Contacts in Android apps could take this question as a solid starting point. Thank you stackoverflow community.
Here is a good starting point
http://developer.android.com/resources/samples/BusinessCard/index.html
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;
}
I make a BroadcastReceiver to receive Phone number of the person who call me
<intent-filter>
<action
android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
How to check if the phone number receive is on my contact list ?
Do you have a tip to know if this phone number exist on contact list with out loading contact list ?
I don't want more information, just if this phone number exist.
If it's not possible, and I must load contact list, how to do it on BroadcastReceiver ?
When I try to do getContentResolver, it's not working because I'm on BroadcastReceiver and not inside Activity...
Thanks for your help
public boolean contactExists(Context context, String number) {
// number is the phone number
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()) {
cur.close();
return true;
}
} finally {
if (cur != null)
cur.close();
}
}
return false;
}
I think it's important to say that you need to add the following in your manifest file.
<uses-permission android:name="android.permission.READ_CONTACTS" />
for 1 you should have a look at the recommended ContactsContract.PhoneLookup provider
A table that represents the result of looking up a phone number, for example for caller ID. To perform a lookup you must append the number you want to find to CONTENT_FILTER_URI. This query is highly optimized.
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
Cursor mycursor=resolver.query(uri, new String[]{PhoneLookup.DISPLAY_NAME,...
if (mycursor!=null && mycursor.moveToFirst()) {
// record exists
}
for 2 you can use the context from the onReceive method to call methods that belong to Context
ContentResolver cr=context.getContentResolver();
I suggest you to use Phone.CONTENT_FILTER_URI instead of PhoneLookup.CONTENT_FILTER_URI
because PhoneLookup can be empty and you will get no result from time to time (tested on LG-P500, froyo)
The problem on my device happens for example when:
switch to airplane mode
use the default message application to send a sms (will be queued).
use PhoneLookup.CONTENT_FILTER_URI to query for a contact
Not all devices seems to be affected
Using PhoneLookup.CONTENT_FILTER_URI the returned cursor is always empty.
Using Phone.CONTENT_FILTER_URI everything is ok (you find the contact if any).
Therefore I suggest you to always use Phone.* Uris except when you really need to use PhoneLookup.*... Which usually is just address book synchronization related stuff (and most of the times is not what you are interested in).