4
* Copyright (C) 2012 to 2013 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 );
286
cur = _activity.getContentResolver().query(
287
ContactsContract.Data.CONTENT_URI,
289
ContactsContract.Data.RAW_CONTACT_ID,
290
CommonDataKinds.Event.START_DATE,
292
ContactsContract.Data.MIMETYPE + " = '" +
293
CommonDataKinds.Event.CONTENT_ITEM_TYPE + "' AND " +
294
CommonDataKinds.Event.TYPE + " = '" +
295
CommonDataKinds.Event.TYPE_BIRTHDAY + "'",
297
while( cur.moveToNext() ) {
298
Long raw_id = cur.getLong( cur.getColumnIndex(
299
ContactsContract.Data.RAW_CONTACT_ID ) );
300
Long id = raw_to_aggregate_ids.get( raw_id );
303
String birthday = cur.getString( cur.getColumnIndex(
304
CommonDataKinds.Event.START_DATE ) );
306
// add associated data
307
cache.addAssociatedBirthday( id, birthday );
314
public void deleteContact( Long id )
316
Uri contact_uri = ContentUris.withAppendedId(
317
ContactsContract.Contacts.CONTENT_URI, id );
318
_activity.getContentResolver().delete( contact_uri, null, null );
322
public Long addContact( String name ) throws ContactCreationException
324
// create raw contact
325
ContentValues values = new ContentValues();
326
Uri contact_uri = _activity.getContentResolver().insert(
327
ContactsContract.RawContacts.CONTENT_URI, values);
328
Long raw_id = ContentUris.parseId( contact_uri );
329
if( raw_id == 0 ) throw new ContactCreationException();
331
// add name data for this raw contact
333
values.put( ContactsContract.Data.RAW_CONTACT_ID, raw_id );
334
values.put( ContactsContract.Data.MIMETYPE,
335
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE );
336
values.put( CommonDataKinds.StructuredName.DISPLAY_NAME, name );
337
_activity.getContentResolver().insert(
338
ContactsContract.Data.CONTENT_URI, values );
341
// find corresponding aggregate contact
342
contact_uri = Uri.withAppendedPath(
343
ContentUris.withAppendedId(
344
ContactsContract.RawContacts.CONTENT_URI, raw_id ),
345
ContactsContract.RawContacts.Entity.CONTENT_DIRECTORY );
346
Cursor cur = _activity.getContentResolver().query( contact_uri,
348
ContactsContract.RawContacts.CONTACT_ID,
349
}, null, null, null );
351
if( cur.moveToNext() )
353
cur.getColumnIndex( ContactsContract.RawContacts.CONTACT_ID ) );
355
if( id == null || id == 0 )
357
// we didn't find an aggregate contact id, so try to clean up (by
358
// deleting the raw contact we just created) before bailing
359
contact_uri = ContentUris.withAppendedId(
360
ContactsContract.RawContacts.CONTENT_URI, id );
361
_activity.getContentResolver().delete( contact_uri, null, null );
363
throw new ContactCreationException();
370
* Obtain the raw contact id for the phone-only raw contact that is
371
* associated with the aggregate contact id. One will be created if
374
* @param id the aggregate contact id
375
* @return the raw contact id
376
* @throws ContactCreationException
378
Long obtainRawContact( Long id ) throws ContactCreationException
380
// attempt to lookup cached value
381
Long raw_id = _aggregate_to_raw_ids.get( id );
382
if( raw_id != null ) return raw_id;
384
// find a corresponding raw contact that has no account name/type
385
Cursor cur = _activity.getContentResolver().query(
386
ContactsContract.RawContacts.CONTENT_URI,
388
ContactsContract.RawContacts._ID,
389
ContactsContract.RawContacts.ACCOUNT_NAME,
391
ContactsContract.RawContacts.DELETED + " = 0 AND " +
392
ContactsContract.RawContacts.CONTACT_ID + " = ? AND " +
393
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_NAME +
395
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_TYPE +
398
String.valueOf( id ),
400
if( cur.moveToNext() )
401
raw_id = cur.getLong(
402
cur.getColumnIndex( ContactsContract.RawContacts._ID ) );
405
// if one wasn't found, we'll need to create one
406
if( raw_id == null ) {
407
ContentValues values = new ContentValues();
408
Uri contact_uri = _activity.getContentResolver().insert(
409
ContactsContract.RawContacts.CONTENT_URI, values);
410
raw_id = ContentUris.parseId( contact_uri );
411
if( raw_id == 0 ) throw new ContactCreationException();
414
// save value in our cache
415
_aggregate_to_raw_ids.put( id, raw_id );
419
private int convertTypeToBackendType( Class< ? > cls, int type )
420
throws ContactCreationException
422
if( cls == CommonDataKinds.Phone.class )
426
case ContactData.TYPE_HOME:
427
return CommonDataKinds.Phone.TYPE_HOME;
428
case ContactData.TYPE_WORK:
429
return CommonDataKinds.Phone.TYPE_WORK;
430
case ContactData.TYPE_MOBILE:
431
return CommonDataKinds.Phone.TYPE_MOBILE;
432
case ContactData.TYPE_FAX_HOME:
433
return CommonDataKinds.Phone.TYPE_FAX_HOME;
434
case ContactData.TYPE_FAX_WORK:
435
return CommonDataKinds.Phone.TYPE_FAX_WORK;
436
case ContactData.TYPE_PAGER:
437
return CommonDataKinds.Phone.TYPE_PAGER;
440
else if( cls == CommonDataKinds.Email.class )
444
case ContactData.TYPE_HOME:
445
return CommonDataKinds.Email.TYPE_HOME;
446
case ContactData.TYPE_WORK:
447
return CommonDataKinds.Email.TYPE_WORK;
450
else if( cls == CommonDataKinds.StructuredPostal.class )
454
case ContactData.TYPE_HOME:
455
return CommonDataKinds.StructuredPostal.TYPE_HOME;
456
case ContactData.TYPE_WORK:
457
return CommonDataKinds.StructuredPostal.TYPE_WORK;
462
throw new ContactCreationException();
466
public void addContactPhone( Long id, String number,
467
ContactData.PreferredDetail data ) throws ContactCreationException
469
ContentValues values = new ContentValues();
470
values.put( ContactsContract.Data.RAW_CONTACT_ID,
471
obtainRawContact( id ) );
472
values.put( ContactsContract.Data.MIMETYPE,
473
CommonDataKinds.Phone.CONTENT_ITEM_TYPE );
474
values.put( CommonDataKinds.Phone.TYPE,
475
convertTypeToBackendType( CommonDataKinds.Phone.class,
477
values.put( CommonDataKinds.Phone.NUMBER, number );
478
if( data.isPreferred() )
479
values.put( CommonDataKinds.Phone.IS_PRIMARY, 1 );
481
_activity.getContentResolver().insert(
482
ContactsContract.Data.CONTENT_URI, values );
486
public void addContactEmail( Long id, String email,
487
ContactData.PreferredDetail data ) throws ContactCreationException
489
ContentValues values = new ContentValues();
490
values.put( ContactsContract.Data.RAW_CONTACT_ID,
491
obtainRawContact( id ) );
492
values.put( ContactsContract.Data.MIMETYPE,
493
CommonDataKinds.Email.CONTENT_ITEM_TYPE );
494
values.put( CommonDataKinds.Email.TYPE,
495
convertTypeToBackendType( CommonDataKinds.Email.class,
497
values.put( CommonDataKinds.Email.DATA, email );
498
if( data.isPreferred() )
499
values.put( CommonDataKinds.Email.IS_PRIMARY, 1 );
501
_activity.getContentResolver().insert(
502
ContactsContract.Data.CONTENT_URI, values );
506
public void addContactAddresses( Long id, String address,
507
ContactData.TypeDetail data ) throws ContactCreationException
509
ContentValues values = new ContentValues();
510
values.put( ContactsContract.Data.RAW_CONTACT_ID,
511
obtainRawContact( id ) );
512
values.put( ContactsContract.Data.MIMETYPE,
513
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE );
514
values.put( CommonDataKinds.StructuredPostal.TYPE,
515
convertTypeToBackendType( CommonDataKinds.StructuredPostal.class,
518
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, address );
520
_activity.getContentResolver().insert(
521
ContactsContract.Data.CONTENT_URI, values );
525
public void addContactOrganisation( Long id, String organisation,
526
ContactData.ExtraDetail data ) throws ContactCreationException
528
ContentValues values = new ContentValues();
529
values.put( ContactsContract.Data.RAW_CONTACT_ID,
530
obtainRawContact( id ) );
531
values.put( ContactsContract.Data.MIMETYPE,
532
CommonDataKinds.Organization.CONTENT_ITEM_TYPE );
533
values.put( CommonDataKinds.Organization.TYPE,
534
CommonDataKinds.Organization.TYPE_WORK );
536
CommonDataKinds.Organization.COMPANY, organisation );
537
if( data.getExtra() != null )
538
values.put( CommonDataKinds.Organization.TITLE, data.getExtra() );
540
_activity.getContentResolver().insert(
541
ContactsContract.Data.CONTENT_URI, values );
545
public void addContactNote( Long id, String note )
546
throws ContactCreationException
548
ContentValues values = new ContentValues();
549
values.put( ContactsContract.Data.RAW_CONTACT_ID,
550
obtainRawContact( id ) );
551
values.put( ContactsContract.Data.MIMETYPE,
552
CommonDataKinds.Note.CONTENT_ITEM_TYPE );
554
CommonDataKinds.Note.NOTE, note );
556
_activity.getContentResolver().insert(
557
ContactsContract.Data.CONTENT_URI, values );
561
public void addContactBirthday( Long id, String birthday )
562
throws ContactCreationException
564
ContentValues values = new ContentValues();
565
values.put( ContactsContract.Data.RAW_CONTACT_ID,
566
obtainRawContact( id ) );
567
values.put( ContactsContract.Data.MIMETYPE,
568
CommonDataKinds.Event.CONTENT_ITEM_TYPE );
570
CommonDataKinds.Event.TYPE, CommonDataKinds.Event.TYPE_BIRTHDAY );
572
CommonDataKinds.Event.START_DATE, birthday );
573
_activity.getContentResolver().insert(
574
ContactsContract.Data.CONTENT_URI, values );