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 );
102
String name = cur.getString( cur.getColumnIndex(
103
CommonDataKinds.StructuredName.DISPLAY_NAME ) );
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
CacheIdentifier cache_identifier = CacheIdentifier.factory(
109
CacheIdentifier.Type.NAME, name );
110
if( cache_identifier != null ) {
111
cache.addLookup( cache_identifier, id );
112
unadded_ids.remove( id );
118
// get contact organisations, primary ones first
119
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
121
ContactsContract.Data.RAW_CONTACT_ID,
122
CommonDataKinds.Organization.COMPANY,
124
ContactsContract.Data.MIMETYPE + " = '" +
125
CommonDataKinds.Organization.CONTENT_ITEM_TYPE + "'",
126
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
127
while( cur.moveToNext() ) {
128
Long raw_id = cur.getLong( cur.getColumnIndex(
129
ContactsContract.Data.RAW_CONTACT_ID ) );
130
Long id = raw_to_aggregate_ids.get( raw_id );
133
String organisation = cur.getString( cur.getColumnIndex(
134
CommonDataKinds.Organization.COMPANY ) );
136
// if this is an organisation name for a contact for whom we
137
// have not added a lookup, add a lookup for the contact id
139
if( unadded_ids.contains( id ) ) {
140
CacheIdentifier cache_identifier = CacheIdentifier.factory(
141
CacheIdentifier.Type.ORGANISATION, organisation );
142
if( cache_identifier != null ) {
143
cache.addLookup( cache_identifier, id );
144
unadded_ids.remove( id );
148
// add associated data
149
cache.addAssociatedOrganisation( id, organisation );
153
// get all phone numbers, primary ones first
154
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
156
ContactsContract.Data.RAW_CONTACT_ID,
157
CommonDataKinds.Phone.NUMBER,
159
ContactsContract.Data.MIMETYPE + " = '" +
160
CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'",
161
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
162
while( cur.moveToNext() ) {
163
Long raw_id = cur.getLong( cur.getColumnIndex(
164
ContactsContract.Data.RAW_CONTACT_ID ) );
165
Long id = raw_to_aggregate_ids.get( raw_id );
168
String number = cur.getString( cur.getColumnIndex(
169
CommonDataKinds.Phone.NUMBER ) );
171
// if this is a number for a contact for whom we have not
172
// added a lookup, add a lookup for the contact id by phone
174
if( unadded_ids.contains( id ) ) {
175
CacheIdentifier cache_identifier = CacheIdentifier.factory(
176
CacheIdentifier.Type.PRIMARY_NUMBER, number );
177
if( cache_identifier != null ) {
178
cache.addLookup( cache_identifier, id );
179
unadded_ids.remove( id );
183
// add associated data
184
cache.addAssociatedNumber( id, number );
188
// get all email addresses, primary ones first
189
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
191
ContactsContract.Data.RAW_CONTACT_ID,
192
CommonDataKinds.Email.DATA,
194
ContactsContract.Data.MIMETYPE + " = '" +
195
CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'",
196
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
197
while( cur.moveToNext() ) {
198
Long raw_id = cur.getLong( cur.getColumnIndex(
199
ContactsContract.Data.RAW_CONTACT_ID ) );
200
Long id = raw_to_aggregate_ids.get( raw_id );
203
String email = cur.getString( cur.getColumnIndex(
204
CommonDataKinds.Email.DATA ) );
206
// if this is an email address for a contact for whom we have
207
// not added a lookup, add a lookup for the contact id by email
209
if( unadded_ids.contains( id ) ) {
210
CacheIdentifier cache_identifier = CacheIdentifier.factory(
211
CacheIdentifier.Type.PRIMARY_EMAIL, email );
212
if( cache_identifier != null ) {
213
cache.addLookup( cache_identifier, id );
214
unadded_ids.remove( id );
218
// add associated data
219
cache.addAssociatedEmail( id, email );
223
// get all postal addresses, primary ones first
224
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
226
ContactsContract.Data.RAW_CONTACT_ID,
227
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS,
229
ContactsContract.Data.MIMETYPE + " = '" +
230
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + "'",
231
null, ContactsContract.Data.IS_PRIMARY + " DESC" );
232
while( cur.moveToNext() ) {
233
Long raw_id = cur.getLong( cur.getColumnIndex(
234
ContactsContract.Data.RAW_CONTACT_ID ) );
235
Long id = raw_to_aggregate_ids.get( raw_id );
238
String address = cur.getString( cur.getColumnIndex(
239
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS ) );
241
// add associated data
242
cache.addAssociatedAddress( id, address );
247
cur = _activity.managedQuery( ContactsContract.Data.CONTENT_URI,
249
ContactsContract.Data.RAW_CONTACT_ID,
250
CommonDataKinds.Note.NOTE,
252
ContactsContract.Data.MIMETYPE + " = '" +
253
CommonDataKinds.Note.CONTENT_ITEM_TYPE + "'",
255
while( cur.moveToNext() ) {
256
Long raw_id = cur.getLong( cur.getColumnIndex(
257
ContactsContract.Data.RAW_CONTACT_ID ) );
258
Long id = raw_to_aggregate_ids.get( raw_id );
261
String note = cur.getString( cur.getColumnIndex(
262
CommonDataKinds.Note.NOTE ) );
264
// add associated data
265
cache.addAssociatedNote( id, note );
271
public void deleteContact( Long id )
273
Uri contact_uri = ContentUris.withAppendedId(
274
ContactsContract.Contacts.CONTENT_URI, id );
275
_activity.getContentResolver().delete( contact_uri, null, null );
279
public Long addContact( String name ) throws ContactCreationException
281
// create raw contact
282
ContentValues values = new ContentValues();
283
Uri contact_uri = _activity.getContentResolver().insert(
284
ContactsContract.RawContacts.CONTENT_URI, values);
285
Long raw_id = ContentUris.parseId( contact_uri );
286
if( raw_id == 0 ) throw new ContactCreationException();
288
// add name data for this raw contact
290
values.put( ContactsContract.Data.RAW_CONTACT_ID, raw_id );
291
values.put( ContactsContract.Data.MIMETYPE,
292
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE );
293
values.put( CommonDataKinds.StructuredName.DISPLAY_NAME, name );
294
_activity.getContentResolver().insert(
295
ContactsContract.Data.CONTENT_URI, values );
298
// find corresponding aggregate contact
299
contact_uri = Uri.withAppendedPath(
300
ContentUris.withAppendedId(
301
ContactsContract.RawContacts.CONTENT_URI, raw_id ),
302
ContactsContract.RawContacts.Entity.CONTENT_DIRECTORY );
303
Cursor cur = _activity.managedQuery( contact_uri,
305
ContactsContract.RawContacts.CONTACT_ID,
306
}, null, null, null );
308
if( cur.moveToNext() )
310
cur.getColumnIndex( ContactsContract.RawContacts.CONTACT_ID ) );
311
if( id == null || id == 0 )
313
// we didn't find an aggregate contact id, so try to clean up (by
314
// deleting the raw contact we just created) before bailing
315
contact_uri = ContentUris.withAppendedId(
316
ContactsContract.RawContacts.CONTENT_URI, id );
317
_activity.getContentResolver().delete( contact_uri, null, null );
319
throw new ContactCreationException();
326
* Obtain the raw contact id for the phone-only raw contact that is
327
* associated with the aggregate contact id. One will be created if
329
* @param id the aggregate contact id
330
* @return the raw contact id
331
* @throws ContactCreationException
333
Long obtainRawContact( Long id ) throws ContactCreationException
335
// attempt to lookup cached value
336
Long raw_id = _aggregate_to_raw_ids.get( id );
337
if( raw_id != null ) return raw_id;
339
// find a corresponding raw contact that has no account name/type
340
Cursor cur = _activity.managedQuery(
341
ContactsContract.RawContacts.CONTENT_URI,
343
ContactsContract.RawContacts._ID,
344
ContactsContract.RawContacts.ACCOUNT_NAME,
346
ContactsContract.RawContacts.DELETED + " = 0 AND " +
347
ContactsContract.RawContacts.CONTACT_ID + " = ? AND " +
348
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_NAME +
350
"IFNULL( " + ContactsContract.RawContacts.ACCOUNT_TYPE +
353
String.valueOf( id ),
355
if( cur.moveToNext() )
356
raw_id = cur.getLong(
357
cur.getColumnIndex( ContactsContract.RawContacts._ID ) );
359
// if one wasn't found, we'll need to create one
360
if( raw_id == null ) {
361
ContentValues values = new ContentValues();
362
Uri contact_uri = _activity.getContentResolver().insert(
363
ContactsContract.RawContacts.CONTENT_URI, values);
364
raw_id = ContentUris.parseId( contact_uri );
365
if( raw_id == 0 ) throw new ContactCreationException();
368
// save value in our cache
369
_aggregate_to_raw_ids.put( id, raw_id );
373
private int convertTypeToBackendType( Class< ? > cls, int type )
374
throws ContactCreationException
376
if( cls == CommonDataKinds.Phone.class )
380
case ContactData.TYPE_HOME:
381
return CommonDataKinds.Phone.TYPE_HOME;
382
case ContactData.TYPE_WORK:
383
return CommonDataKinds.Phone.TYPE_WORK;
384
case ContactData.TYPE_MOBILE:
385
return CommonDataKinds.Phone.TYPE_MOBILE;
386
case ContactData.TYPE_FAX_HOME:
387
return CommonDataKinds.Phone.TYPE_FAX_HOME;
388
case ContactData.TYPE_FAX_WORK:
389
return CommonDataKinds.Phone.TYPE_FAX_WORK;
390
case ContactData.TYPE_PAGER:
391
return CommonDataKinds.Phone.TYPE_PAGER;
394
else if( cls == CommonDataKinds.Email.class )
398
case ContactData.TYPE_HOME:
399
return CommonDataKinds.Email.TYPE_HOME;
400
case ContactData.TYPE_WORK:
401
return CommonDataKinds.Email.TYPE_WORK;
404
else if( cls == CommonDataKinds.StructuredPostal.class )
408
case ContactData.TYPE_HOME:
409
return CommonDataKinds.StructuredPostal.TYPE_HOME;
410
case ContactData.TYPE_WORK:
411
return CommonDataKinds.StructuredPostal.TYPE_WORK;
416
throw new ContactCreationException();
420
public void addContactPhone( Long id, String number,
421
ContactData.PreferredDetail data ) throws ContactCreationException
423
ContentValues values = new ContentValues();
424
values.put( ContactsContract.Data.RAW_CONTACT_ID,
425
obtainRawContact( id ) );
426
values.put( ContactsContract.Data.MIMETYPE,
427
CommonDataKinds.Phone.CONTENT_ITEM_TYPE );
428
values.put( CommonDataKinds.Phone.TYPE,
429
convertTypeToBackendType( CommonDataKinds.Phone.class,
431
values.put( CommonDataKinds.Phone.NUMBER, number );
432
if( data.isPreferred() )
433
values.put( CommonDataKinds.Phone.IS_PRIMARY, 1 );
435
_activity.getContentResolver().insert(
436
ContactsContract.Data.CONTENT_URI, values );
440
public void addContactEmail( Long id, String email,
441
ContactData.PreferredDetail 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.Email.CONTENT_ITEM_TYPE );
448
values.put( CommonDataKinds.Email.TYPE,
449
convertTypeToBackendType( CommonDataKinds.Email.class,
451
values.put( CommonDataKinds.Email.DATA, email );
452
if( data.isPreferred() )
453
values.put( CommonDataKinds.Email.IS_PRIMARY, 1 );
455
_activity.getContentResolver().insert(
456
ContactsContract.Data.CONTENT_URI, values );
460
public void addContactAddresses( Long id, String address,
461
ContactData.TypeDetail data ) throws ContactCreationException
463
ContentValues values = new ContentValues();
464
values.put( ContactsContract.Data.RAW_CONTACT_ID,
465
obtainRawContact( id ) );
466
values.put( ContactsContract.Data.MIMETYPE,
467
CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE );
468
values.put( CommonDataKinds.StructuredPostal.TYPE,
469
convertTypeToBackendType( CommonDataKinds.StructuredPostal.class,
472
CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, address );
474
_activity.getContentResolver().insert(
475
ContactsContract.Data.CONTENT_URI, values );
479
public void addContactOrganisation( Long id, String organisation,
480
ContactData.ExtraDetail 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.Organization.CONTENT_ITEM_TYPE );
487
values.put( CommonDataKinds.Organization.TYPE,
488
CommonDataKinds.Organization.TYPE_WORK );
490
CommonDataKinds.Organization.COMPANY, organisation );
491
if( data.getExtra() != null )
492
values.put( CommonDataKinds.Organization.TITLE, data.getExtra() );
494
_activity.getContentResolver().insert(
495
ContactsContract.Data.CONTENT_URI, values );
499
public void addContactNote( Long id, String note )
500
throws ContactCreationException
502
ContentValues values = new ContentValues();
503
values.put( ContactsContract.Data.RAW_CONTACT_ID,
504
obtainRawContact( id ) );
505
values.put( ContactsContract.Data.MIMETYPE,
506
CommonDataKinds.Note.CONTENT_ITEM_TYPE );
508
CommonDataKinds.Note.NOTE, note );
510
_activity.getContentResolver().insert(
511
ContactsContract.Data.CONTENT_URI, values );