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.getContentResolver().query(
61
ContactsContract.Contacts.CONTENT_URI,
63
ContactsContract.Contacts._ID,
64
}, null, null, null );
65
while( cur.moveToNext() ) {
66
Long id = cur.getLong(
67
cur.getColumnIndex( ContactsContract.Contacts._ID ) );
68
unadded_ids.add( id );
72
// build a mapping of the ids of raw contacts to the ids of their
74
HashMap< Long, Long > raw_to_aggregate_ids =
75
new HashMap< Long, Long >();
76
cur = _activity.getContentResolver().query(
77
ContactsContract.RawContacts.CONTENT_URI,
79
ContactsContract.RawContacts._ID,
80
ContactsContract.RawContacts.CONTACT_ID,
81
}, ContactsContract.RawContacts.DELETED + " = 0", null, null );
82
while( cur.moveToNext() ) {
83
Long raw_id = cur.getLong(
84
cur.getColumnIndex( ContactsContract.RawContacts._ID ) );
85
Long id = cur.getLong(
86
cur.getColumnIndex( ContactsContract.RawContacts.CONTACT_ID ) );
87
raw_to_aggregate_ids.put( raw_id, id );
91
// get structured names, primary ones first
92
cur = _activity.getContentResolver().query(
93
ContactsContract.Data.CONTENT_URI,
95
ContactsContract.Data.RAW_CONTACT_ID,
96
CommonDataKinds.StructuredName.DISPLAY_NAME,
98
ContactsContract.Data.MIMETYPE + " = '" +
99
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'",
100
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
101
while( cur.moveToNext() ) {
102
Long raw_id = cur.getLong( cur.getColumnIndex(
103
ContactsContract.Data.RAW_CONTACT_ID ) );
104
Long id = raw_to_aggregate_ids.get( raw_id );
107
String name = cur.getString( cur.getColumnIndex(
108
CommonDataKinds.StructuredName.DISPLAY_NAME ) );
110
// if this is a name for a contact for whom we have not added a
111
// lookup, add a lookup for the contact id by name
112
if( unadded_ids.contains( id ) ) {
113
CacheIdentifier cache_identifier = CacheIdentifier.factory(
114
CacheIdentifier.Type.NAME, name );
115
if( cache_identifier != null ) {
116
cache.addLookup( cache_identifier, id );
117
unadded_ids.remove( id );
124
// get contact organisations, primary ones first
125
cur = _activity.getContentResolver().query(
126
ContactsContract.Data.CONTENT_URI,
128
ContactsContract.Data.RAW_CONTACT_ID,
129
CommonDataKinds.Organization.COMPANY,
131
ContactsContract.Data.MIMETYPE + " = '" +
132
CommonDataKinds.Organization.CONTENT_ITEM_TYPE + "'",
133
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
134
while( cur.moveToNext() ) {
135
Long raw_id = cur.getLong( cur.getColumnIndex(
136
ContactsContract.Data.RAW_CONTACT_ID ) );
137
Long id = raw_to_aggregate_ids.get( raw_id );
140
String organisation = cur.getString( cur.getColumnIndex(
141
CommonDataKinds.Organization.COMPANY ) );
143
// if this is an organisation name for a contact for whom we
144
// have not added a lookup, add a lookup for the contact id
146
if( unadded_ids.contains( id ) ) {
147
CacheIdentifier cache_identifier = CacheIdentifier.factory(
148
CacheIdentifier.Type.ORGANISATION, organisation );
149
if( cache_identifier != null ) {
150
cache.addLookup( cache_identifier, id );
151
unadded_ids.remove( id );
155
// add associated data
156
cache.addAssociatedOrganisation( id, organisation );
161
// get all phone numbers, primary ones first
162
cur = _activity.getContentResolver().query(
163
ContactsContract.Data.CONTENT_URI,
165
ContactsContract.Data.RAW_CONTACT_ID,
166
CommonDataKinds.Phone.NUMBER,
168
ContactsContract.Data.MIMETYPE + " = '" +
169
CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'",
170
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
171
while( cur.moveToNext() ) {
172
Long raw_id = cur.getLong( cur.getColumnIndex(
173
ContactsContract.Data.RAW_CONTACT_ID ) );
174
Long id = raw_to_aggregate_ids.get( raw_id );
177
String number = cur.getString( cur.getColumnIndex(
178
CommonDataKinds.Phone.NUMBER ) );
180
// if this is a number for a contact for whom we have not
181
// added a lookup, add a lookup for the contact id by phone
183
if( unadded_ids.contains( id ) ) {
184
CacheIdentifier cache_identifier = CacheIdentifier.factory(
185
CacheIdentifier.Type.PRIMARY_NUMBER, number );
186
if( cache_identifier != null ) {
187
cache.addLookup( cache_identifier, id );
188
unadded_ids.remove( id );
192
// add associated data
193
cache.addAssociatedNumber( id, number );
198
// get all email addresses, primary ones first
199
cur = _activity.getContentResolver().query(
200
ContactsContract.Data.CONTENT_URI,
202
ContactsContract.Data.RAW_CONTACT_ID,
203
CommonDataKinds.Email.DATA,
205
ContactsContract.Data.MIMETYPE + " = '" +
206
CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'",
207
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
208
while( cur.moveToNext() ) {
209
Long raw_id = cur.getLong( cur.getColumnIndex(
210
ContactsContract.Data.RAW_CONTACT_ID ) );
211
Long id = raw_to_aggregate_ids.get( raw_id );
214
String email = cur.getString( cur.getColumnIndex(
215
CommonDataKinds.Email.DATA ) );
217
// if this is an email address for a contact for whom we have
218
// not added a lookup, add a lookup for the contact id by email
220
if( unadded_ids.contains( id ) ) {
221
CacheIdentifier cache_identifier = CacheIdentifier.factory(
222
CacheIdentifier.Type.PRIMARY_EMAIL, email );
223
if( cache_identifier != null ) {
224
cache.addLookup( cache_identifier, id );
225
unadded_ids.remove( id );
229
// add associated data
230
cache.addAssociatedEmail( id, email );
235
// get all postal addresses, primary ones first
236
cur = _activity.getContentResolver().query(
237
ContactsContract.Data.CONTENT_URI,
239
ContactsContract.Data.RAW_CONTACT_ID,
240
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS,
242
ContactsContract.Data.MIMETYPE + " = '" +
243
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + "'",
244
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
245
while( cur.moveToNext() ) {
246
Long raw_id = cur.getLong( cur.getColumnIndex(
247
ContactsContract.Data.RAW_CONTACT_ID ) );
248
Long id = raw_to_aggregate_ids.get( raw_id );
251
String address = cur.getString( cur.getColumnIndex(
252
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS ) );
254
// add associated data
255
cache.addAssociatedAddress( id, address );
261
cur = _activity.getContentResolver().query(
262
ContactsContract.Data.CONTENT_URI,
264
ContactsContract.Data.RAW_CONTACT_ID,
265
CommonDataKinds.Note.NOTE,
267
ContactsContract.Data.MIMETYPE + " = '" +
268
CommonDataKinds.Note.CONTENT_ITEM_TYPE + "'",
270
while( cur.moveToNext() ) {
271
Long raw_id = cur.getLong( cur.getColumnIndex(
272
ContactsContract.Data.RAW_CONTACT_ID ) );
273
Long id = raw_to_aggregate_ids.get( raw_id );
276
String note = cur.getString( cur.getColumnIndex(
277
CommonDataKinds.Note.NOTE ) );
279
// add associated data
280
cache.addAssociatedNote( id, note );
287
public void deleteContact( Long id )
289
Uri contact_uri = ContentUris.withAppendedId(
290
ContactsContract.Contacts.CONTENT_URI, id );
291
_activity.getContentResolver().delete( contact_uri, null, null );
295
public Long addContact( String name ) throws ContactCreationException
297
// create raw contact
298
ContentValues values = new ContentValues();
299
Uri contact_uri = _activity.getContentResolver().insert(
300
ContactsContract.RawContacts.CONTENT_URI, values);
301
Long raw_id = ContentUris.parseId( contact_uri );
302
if( raw_id == 0 ) throw new ContactCreationException();
304
// add name data for this raw contact
306
values.put( ContactsContract.Data.RAW_CONTACT_ID, raw_id );
307
values.put( ContactsContract.Data.MIMETYPE,
308
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE );
309
values.put( CommonDataKinds.StructuredName.DISPLAY_NAME, name );
310
_activity.getContentResolver().insert(
311
ContactsContract.Data.CONTENT_URI, values );
314
// find corresponding aggregate contact
315
contact_uri = Uri.withAppendedPath(
316
ContentUris.withAppendedId(
317
ContactsContract.RawContacts.CONTENT_URI, raw_id ),
318
ContactsContract.RawContacts.Entity.CONTENT_DIRECTORY );
319
Cursor cur = _activity.getContentResolver().query( contact_uri,
321
ContactsContract.RawContacts.CONTACT_ID,
322
}, null, null, null );
324
if( cur.moveToNext() )
326
cur.getColumnIndex( ContactsContract.RawContacts.CONTACT_ID ) );
328
if( id == null || id == 0 )
330
// we didn't find an aggregate contact id, so try to clean up (by
331
// deleting the raw contact we just created) before bailing
332
contact_uri = ContentUris.withAppendedId(
333
ContactsContract.RawContacts.CONTENT_URI, id );
334
_activity.getContentResolver().delete( contact_uri, null, null );
336
throw new ContactCreationException();
343
* Obtain the raw contact id for the phone-only raw contact that is
344
* associated with the aggregate contact id. One will be created if
347
* @param id the aggregate contact id
348
* @return the raw contact id
349
* @throws ContactCreationException
351
Long obtainRawContact( Long id ) throws ContactCreationException
353
// attempt to lookup cached value
354
Long raw_id = _aggregate_to_raw_ids.get( id );
355
if( raw_id != null ) return raw_id;
357
// find a corresponding raw contact that has no account name/type
358
Cursor cur = _activity.getContentResolver().query(
359
ContactsContract.RawContacts.CONTENT_URI,
361
ContactsContract.RawContacts._ID,
362
ContactsContract.RawContacts.ACCOUNT_NAME,
364
ContactsContract.RawContacts.DELETED + " = 0 AND " +
365
ContactsContract.RawContacts.CONTACT_ID + " = ? AND " +
366
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_NAME +
368
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_TYPE +
371
String.valueOf( id ),
373
if( cur.moveToNext() )
374
raw_id = cur.getLong(
375
cur.getColumnIndex( ContactsContract.RawContacts._ID ) );
378
// if one wasn't found, we'll need to create one
379
if( raw_id == null ) {
380
ContentValues values = new ContentValues();
381
Uri contact_uri = _activity.getContentResolver().insert(
382
ContactsContract.RawContacts.CONTENT_URI, values);
383
raw_id = ContentUris.parseId( contact_uri );
384
if( raw_id == 0 ) throw new ContactCreationException();
387
// save value in our cache
388
_aggregate_to_raw_ids.put( id, raw_id );
392
private int convertTypeToBackendType( Class< ? > cls, int type )
393
throws ContactCreationException
395
if( cls == CommonDataKinds.Phone.class )
399
case ContactData.TYPE_HOME:
400
return CommonDataKinds.Phone.TYPE_HOME;
401
case ContactData.TYPE_WORK:
402
return CommonDataKinds.Phone.TYPE_WORK;
403
case ContactData.TYPE_MOBILE:
404
return CommonDataKinds.Phone.TYPE_MOBILE;
405
case ContactData.TYPE_FAX_HOME:
406
return CommonDataKinds.Phone.TYPE_FAX_HOME;
407
case ContactData.TYPE_FAX_WORK:
408
return CommonDataKinds.Phone.TYPE_FAX_WORK;
409
case ContactData.TYPE_PAGER:
410
return CommonDataKinds.Phone.TYPE_PAGER;
413
else if( cls == CommonDataKinds.Email.class )
417
case ContactData.TYPE_HOME:
418
return CommonDataKinds.Email.TYPE_HOME;
419
case ContactData.TYPE_WORK:
420
return CommonDataKinds.Email.TYPE_WORK;
423
else if( cls == CommonDataKinds.StructuredPostal.class )
427
case ContactData.TYPE_HOME:
428
return CommonDataKinds.StructuredPostal.TYPE_HOME;
429
case ContactData.TYPE_WORK:
430
return CommonDataKinds.StructuredPostal.TYPE_WORK;
435
throw new ContactCreationException();
439
public void addContactPhone( Long id, String number,
440
ContactData.PreferredDetail data ) throws ContactCreationException
442
ContentValues values = new ContentValues();
443
values.put( ContactsContract.Data.RAW_CONTACT_ID,
444
obtainRawContact( id ) );
445
values.put( ContactsContract.Data.MIMETYPE,
446
CommonDataKinds.Phone.CONTENT_ITEM_TYPE );
447
values.put( CommonDataKinds.Phone.TYPE,
448
convertTypeToBackendType( CommonDataKinds.Phone.class,
450
values.put( CommonDataKinds.Phone.NUMBER, number );
451
if( data.isPreferred() )
452
values.put( CommonDataKinds.Phone.IS_PRIMARY, 1 );
454
_activity.getContentResolver().insert(
455
ContactsContract.Data.CONTENT_URI, values );
459
public void addContactEmail( Long id, String email,
460
ContactData.PreferredDetail data ) throws ContactCreationException
462
ContentValues values = new ContentValues();
463
values.put( ContactsContract.Data.RAW_CONTACT_ID,
464
obtainRawContact( id ) );
465
values.put( ContactsContract.Data.MIMETYPE,
466
CommonDataKinds.Email.CONTENT_ITEM_TYPE );
467
values.put( CommonDataKinds.Email.TYPE,
468
convertTypeToBackendType( CommonDataKinds.Email.class,
470
values.put( CommonDataKinds.Email.DATA, email );
471
if( data.isPreferred() )
472
values.put( CommonDataKinds.Email.IS_PRIMARY, 1 );
474
_activity.getContentResolver().insert(
475
ContactsContract.Data.CONTENT_URI, values );
479
public void addContactAddresses( Long id, String address,
480
ContactData.TypeDetail data ) throws ContactCreationException
482
ContentValues values = new ContentValues();
483
values.put( ContactsContract.Data.RAW_CONTACT_ID,
484
obtainRawContact( id ) );
485
values.put( ContactsContract.Data.MIMETYPE,
486
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE );
487
values.put( CommonDataKinds.StructuredPostal.TYPE,
488
convertTypeToBackendType( CommonDataKinds.StructuredPostal.class,
491
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, address );
493
_activity.getContentResolver().insert(
494
ContactsContract.Data.CONTENT_URI, values );
498
public void addContactOrganisation( Long id, String organisation,
499
ContactData.ExtraDetail data ) throws ContactCreationException
501
ContentValues values = new ContentValues();
502
values.put( ContactsContract.Data.RAW_CONTACT_ID,
503
obtainRawContact( id ) );
504
values.put( ContactsContract.Data.MIMETYPE,
505
CommonDataKinds.Organization.CONTENT_ITEM_TYPE );
506
values.put( CommonDataKinds.Organization.TYPE,
507
CommonDataKinds.Organization.TYPE_WORK );
509
CommonDataKinds.Organization.COMPANY, organisation );
510
if( data.getExtra() != null )
511
values.put( CommonDataKinds.Organization.TITLE, data.getExtra() );
513
_activity.getContentResolver().insert(
514
ContactsContract.Data.CONTENT_URI, values );
518
public void addContactNote( Long id, String note )
519
throws ContactCreationException
521
ContentValues values = new ContentValues();
522
values.put( ContactsContract.Data.RAW_CONTACT_ID,
523
obtainRawContact( id ) );
524
values.put( ContactsContract.Data.MIMETYPE,
525
CommonDataKinds.Note.CONTENT_ITEM_TYPE );
527
CommonDataKinds.Note.NOTE, note );
529
_activity.getContentResolver().insert(
530
ContactsContract.Data.CONTENT_URI, values );