4
* Copyright (C) 2012 Tim Marston <tim@ed.am>
6
* This file is part of the Import Contacts program (hereafter referred
7
* to as "this program"). For more information, see
8
* http://ed.am/dev/android/import-contacts
10
* This program is free software: you can redistribute it and/or modify
11
* it under the terms of the GNU General Public License as published by
12
* the Free Software Foundation, either version 3 of the License, or
13
* (at your option) any later version.
15
* This program is distributed in the hope that it will be useful,
16
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
* GNU General Public License for more details.
20
* You should have received a copy of the GNU General Public License
21
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24
package am.ed.importcontacts;
26
import java.util.HashMap;
27
import java.util.HashSet;
29
import am.ed.importcontacts.ContactsCache.CacheIdentifier;
30
import am.ed.importcontacts.Importer.ContactData;
31
import android.annotation.TargetApi;
32
import android.app.Activity;
33
import android.content.ContentUris;
34
import android.content.ContentValues;
35
import android.database.Cursor;
36
import android.net.Uri;
37
import android.provider.ContactsContract;
38
import android.provider.ContactsContract.CommonDataKinds;
41
public class ContactsContractBackend implements Backend
43
private Activity _activity = null;
44
private HashMap< Long, Long > _aggregate_to_raw_ids = null;
46
ContactsContractBackend( Activity activity )
49
_aggregate_to_raw_ids = new HashMap< Long, Long >();
53
public void populateCache( ContactsCache cache )
57
// build a set of aggregate contact ids that haven't been added to the
59
HashSet< Long > unadded_ids = new HashSet< Long >();
60
cur = _activity.managedQuery( ContactsContract.Contacts.CONTENT_URI,
62
ContactsContract.Contacts._ID,
63
}, null, null, null );
64
while( cur.moveToNext() ) {
65
Long id = cur.getLong(
66
cur.getColumnIndex( ContactsContract.Contacts._ID ) );
67
unadded_ids.add( id );
70
// build a mapping of the ids of raw contacts to the ids of their
72
HashMap< Long, Long > raw_to_aggregate_ids =
73
new HashMap< Long, Long >();
74
cur = _activity.managedQuery( ContactsContract.RawContacts.CONTENT_URI,
76
ContactsContract.RawContacts._ID,
77
ContactsContract.RawContacts.CONTACT_ID,
78
}, ContactsContract.RawContacts.DELETED + " != 0", null, null );
79
while( cur.moveToNext() ) {
80
Long raw_id = cur.getLong(
81
cur.getColumnIndex( ContactsContract.RawContacts._ID ) );
82
Long id = cur.getLong(
83
cur.getColumnIndex( ContactsContract.RawContacts.CONTACT_ID ) );
84
raw_to_aggregate_ids.put( raw_id, id );
87
// get structured names, primary ones first
88
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
90
ContactsContract.Data.RAW_CONTACT_ID,
91
CommonDataKinds.StructuredName.DISPLAY_NAME,
93
ContactsContract.Data.MIMETYPE + " = '" +
94
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'",
95
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
96
while( cur.moveToNext() ) {
97
Long raw_id = cur.getLong( cur.getColumnIndex(
98
ContactsContract.Data.RAW_CONTACT_ID ) );
99
Long id = raw_to_aggregate_ids.get( raw_id );
100
String name = ContactsCache.normaliseName(
101
cur.getString( cur.getColumnIndex(
102
CommonDataKinds.StructuredName.DISPLAY_NAME ) ) );
103
if( name != null && id != null )
105
// if this is a name for a contact for whom we have not added a
106
// lookup, add a lookup for the contact id by name
107
if( unadded_ids.contains( id ) ) {
108
cache.addLookup( new CacheIdentifier(
109
CacheIdentifier.Type.NAME, name ), id );
110
unadded_ids.remove( id );
115
// get contact organisations, primary ones first
116
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
118
ContactsContract.Data.RAW_CONTACT_ID,
119
CommonDataKinds.Organization.COMPANY,
121
ContactsContract.Data.MIMETYPE + " = '" +
122
CommonDataKinds.Organization.CONTENT_ITEM_TYPE + "'",
123
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
124
while( cur.moveToNext() ) {
125
Long raw_id = cur.getLong( cur.getColumnIndex(
126
ContactsContract.Data.RAW_CONTACT_ID ) );
127
Long id = raw_to_aggregate_ids.get( raw_id );
128
String organisation = ContactsCache.normaliseOrganisation(
129
cur.getString( cur.getColumnIndex(
130
CommonDataKinds.Organization.COMPANY ) ) );
131
if( organisation != null && id != null )
133
// if this is an organisation name for a contact for whom we
134
// have not added a lookup, add a lookup for the contact id
136
if( unadded_ids.contains( id ) ) {
137
cache.addLookup( new CacheIdentifier(
138
CacheIdentifier.Type.ORGANISATION, organisation ), id );
139
unadded_ids.remove( id );
142
// add associated data
143
cache.addAssociatedOrganisation( id, organisation );
147
// get all phone numbers, primary ones first
148
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
150
ContactsContract.Data.RAW_CONTACT_ID,
151
CommonDataKinds.Phone.NUMBER,
153
ContactsContract.Data.MIMETYPE + " = '" +
154
CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'",
155
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
156
while( cur.moveToNext() ) {
157
Long raw_id = cur.getLong( cur.getColumnIndex(
158
ContactsContract.Data.RAW_CONTACT_ID ) );
159
Long id = raw_to_aggregate_ids.get( raw_id );
160
String number = ContactsCache.normalisePhoneNumber(
161
cur.getString( cur.getColumnIndex(
162
CommonDataKinds.Phone.NUMBER ) ) );
163
if( number != null && id != null )
165
// if this is a number for a contact for whom we have not
166
// added a lookup, add a lookup for the contact id by phone
168
if( unadded_ids.contains( id ) ) {
169
cache.addLookup( new CacheIdentifier(
170
CacheIdentifier.Type.PRIMARY_NUMBER, number ), id );
171
unadded_ids.remove( id );
174
// add associated data
175
cache.addAssociatedNumber( id, number );
179
// get all email addresses, primary ones first
180
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
182
ContactsContract.Data.RAW_CONTACT_ID,
183
CommonDataKinds.Email.DATA,
185
ContactsContract.Data.MIMETYPE + " = '" +
186
CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'",
187
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
188
while( cur.moveToNext() ) {
189
Long raw_id = cur.getLong( cur.getColumnIndex(
190
ContactsContract.Data.RAW_CONTACT_ID ) );
191
Long id = raw_to_aggregate_ids.get( raw_id );
192
String email = ContactsCache.normaliseEmailAddress(
193
cur.getString( cur.getColumnIndex(
194
CommonDataKinds.Email.DATA ) ) );
195
if( email != null && id != null )
197
// if this is an email address for a contact for whom we have
198
// not added a lookup, add a lookup for the contact id by email
200
if( unadded_ids.contains( id ) ) {
201
cache.addLookup( new CacheIdentifier(
202
CacheIdentifier.Type.PRIMARY_EMAIL, email ), id );
203
unadded_ids.remove( id );
206
// add associated data
207
cache.addAssociatedEmail( id, email );
211
// get all postal addresses, primary ones first
212
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
214
ContactsContract.Data.RAW_CONTACT_ID,
215
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS,
217
ContactsContract.Data.MIMETYPE + " = '" +
218
CommonDataKinds.Organization.CONTENT_ITEM_TYPE + "'",
219
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
220
while( cur.moveToNext() ) {
221
Long raw_id = cur.getLong( cur.getColumnIndex(
222
ContactsContract.Data.RAW_CONTACT_ID ) );
223
Long id = raw_to_aggregate_ids.get( raw_id );
224
String address = ContactsCache.normaliseAddress(
225
cur.getString( cur.getColumnIndex(
226
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS ) ) );
227
if( address != null && id != null )
229
// add associated data
230
cache.addAssociatedAddress( id, address );
236
public void deleteContact( Long id )
238
Uri contact_uri = ContentUris.withAppendedId(
239
ContactsContract.Contacts.CONTENT_URI, id );
240
_activity.getContentResolver().delete( contact_uri, null, null );
244
public Long addContact( String name ) throws ContactCreationException
246
// create raw contact
247
ContentValues values = new ContentValues();
248
Uri contact_uri = _activity.getContentResolver().insert(
249
ContactsContract.RawContacts.CONTENT_URI, values);
250
Long raw_id = ContentUris.parseId( contact_uri );
251
if( raw_id == 0 ) throw new ContactCreationException();
253
// add name data for this raw contact
255
values.put( ContactsContract.Data.RAW_CONTACT_ID, raw_id );
256
values.put( ContactsContract.Data.MIMETYPE,
257
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE );
258
values.put( CommonDataKinds.StructuredName.DISPLAY_NAME, name );
259
_activity.getContentResolver().insert(
260
ContactsContract.Data.CONTENT_URI, values );
263
// find corresponding aggregate contact
264
contact_uri = Uri.withAppendedPath(
265
ContentUris.withAppendedId(
266
ContactsContract.RawContacts.CONTENT_URI, raw_id ),
267
ContactsContract.RawContacts.Entity.CONTENT_DIRECTORY );
268
Cursor cur = _activity.managedQuery( contact_uri,
270
ContactsContract.RawContacts.CONTACT_ID,
271
}, null, null, null );
273
if( cur.moveToNext() )
275
cur.getColumnIndex( ContactsContract.RawContacts.CONTACT_ID ) );
276
if( id == null || id == 0 )
278
// we didn't find an aggregate contact id, so try to clean up (by
279
// deleting the raw contact we just created) before bailing
280
contact_uri = ContentUris.withAppendedId(
281
ContactsContract.RawContacts.CONTENT_URI, id );
282
_activity.getContentResolver().delete( contact_uri, null, null );
284
throw new ContactCreationException();
291
* Obtain the raw contact id for the phone-only raw contact that is
292
* associated with the aggregate contact id. One will be created if
294
* @param id the aggregate contact id
295
* @return the raw contact id
296
* @throws ContactCreationException
298
Long obtainRawContact( Long id ) throws ContactCreationException
300
// attempt to lookup cached value
301
Long raw_id = _aggregate_to_raw_ids.get( id );
302
if( raw_id != null ) return raw_id;
304
// find a corresponding raw contact that has no account name/type
305
Cursor cur = _activity.managedQuery(
306
ContactsContract.RawContacts.CONTENT_URI,
308
ContactsContract.RawContacts._ID,
310
ContactsContract.RawContacts.DELETED + " != 0 AND " +
311
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_NAME +
313
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_TYPE +
316
if( cur.moveToNext() )
317
raw_id = cur.getLong(
318
cur.getColumnIndex( ContactsContract.RawContacts._ID ) );
320
// if one wasn't found, we'll need to create one
321
if( raw_id == null ) {
322
ContentValues values = new ContentValues();
323
Uri contact_uri = _activity.getContentResolver().insert(
324
ContactsContract.RawContacts.CONTENT_URI, values);
325
raw_id = ContentUris.parseId( contact_uri );
326
if( raw_id == 0 ) throw new ContactCreationException();
329
// save value in our cache
330
_aggregate_to_raw_ids.put( id, raw_id );
334
private int convertTypeToBackendType( Class< ? > cls, int type )
335
throws ContactCreationException
337
if( cls == CommonDataKinds.Phone.class )
341
case ContactData.TYPE_HOME:
342
return CommonDataKinds.Phone.TYPE_HOME;
343
case ContactData.TYPE_WORK:
344
return CommonDataKinds.Phone.TYPE_WORK;
345
case ContactData.TYPE_MOBILE:
346
return CommonDataKinds.Phone.TYPE_MOBILE;
347
case ContactData.TYPE_FAX_HOME:
348
return CommonDataKinds.Phone.TYPE_FAX_HOME;
349
case ContactData.TYPE_FAX_WORK:
350
return CommonDataKinds.Phone.TYPE_FAX_WORK;
351
case ContactData.TYPE_PAGER:
352
return CommonDataKinds.Phone.TYPE_PAGER;
355
else if( cls == CommonDataKinds.Email.class )
359
case ContactData.TYPE_HOME:
360
return CommonDataKinds.Email.TYPE_HOME;
361
case ContactData.TYPE_WORK:
362
return CommonDataKinds.Email.TYPE_WORK;
365
else if( cls == CommonDataKinds.StructuredPostal.class )
369
case ContactData.TYPE_HOME:
370
return CommonDataKinds.StructuredPostal.TYPE_HOME;
371
case ContactData.TYPE_WORK:
372
return CommonDataKinds.StructuredPostal.TYPE_WORK;
377
throw new ContactCreationException();
381
public void addContactPhone( Long id, String number,
382
ContactData.PreferredDetail data ) throws ContactCreationException
384
ContentValues values = new ContentValues();
385
values.put( ContactsContract.Data.RAW_CONTACT_ID,
386
obtainRawContact( id ) );
387
values.put( ContactsContract.Data.MIMETYPE,
388
CommonDataKinds.Phone.CONTENT_ITEM_TYPE );
389
values.put( CommonDataKinds.Phone.TYPE,
390
convertTypeToBackendType( CommonDataKinds.Phone.class,
392
values.put( CommonDataKinds.Phone.NUMBER, number );
393
if( data.isPreferred() )
394
values.put( CommonDataKinds.Phone.IS_PRIMARY, 1 );
396
_activity.getContentResolver().insert(
397
ContactsContract.Data.CONTENT_URI, values );
401
public void addContactEmail( Long id, String email,
402
ContactData.PreferredDetail data ) throws ContactCreationException
404
ContentValues values = new ContentValues();
405
values.put( ContactsContract.Data.RAW_CONTACT_ID,
406
obtainRawContact( id ) );
407
values.put( ContactsContract.Data.MIMETYPE,
408
CommonDataKinds.Email.CONTENT_ITEM_TYPE );
409
values.put( CommonDataKinds.Email.TYPE,
410
convertTypeToBackendType( CommonDataKinds.Email.class,
412
values.put( CommonDataKinds.Email.DATA, email );
413
if( data.isPreferred() )
414
values.put( CommonDataKinds.Email.IS_PRIMARY, 1 );
416
_activity.getContentResolver().insert(
417
ContactsContract.Data.CONTENT_URI, values );
421
public void addContactAddresses( Long id, String address,
422
ContactData.TypeDetail data ) throws ContactCreationException
424
ContentValues values = new ContentValues();
425
values.put( ContactsContract.Data.RAW_CONTACT_ID,
426
obtainRawContact( id ) );
427
values.put( ContactsContract.Data.MIMETYPE,
428
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE );
429
values.put( CommonDataKinds.StructuredPostal.TYPE,
430
convertTypeToBackendType( CommonDataKinds.StructuredPostal.class,
433
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, address );
435
_activity.getContentResolver().insert(
436
ContactsContract.Data.CONTENT_URI, values );
440
public void addContactOrganisation( Long id, String organisation,
441
ContactData.ExtraDetail data ) throws ContactCreationException
443
ContentValues values = new ContentValues();
444
values.put( ContactsContract.Data.RAW_CONTACT_ID,
445
obtainRawContact( id ) );
446
values.put( ContactsContract.Data.MIMETYPE,
447
CommonDataKinds.Organization.CONTENT_ITEM_TYPE );
448
values.put( CommonDataKinds.Organization.TYPE,
449
CommonDataKinds.Organization.TYPE_WORK );
451
CommonDataKinds.Organization.COMPANY, organisation );
452
if( data.getExtra() != null )
453
values.put( CommonDataKinds.Organization.TITLE, data.getExtra() );
455
_activity.getContentResolver().insert(
456
ContactsContract.Data.CONTENT_URI, values );