/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
1
/*
2
 * ContactsBackend.java
3
 *
4
 * Copyright (C) 2012 Tim Marston <tim@ed.am>
5
 *
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
9
 *
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.
14
 *
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.
19
 *
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/>.
22
 */
23
24
package am.ed.importcontacts;
25
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
26
import java.util.HashMap;
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
27
import java.util.HashSet;
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
28
import java.util.Iterator;
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
29
30
import am.ed.importcontacts.ContactsCache.CacheIdentifier;
31
import am.ed.importcontacts.Importer.ContactData;
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.Contacts;
38
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
39
@SuppressWarnings( "deprecation" )
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
40
public class ContactsBackend implements Backend
41
{
42
	private Activity _activity = null;
43
44
	ContactsBackend( Activity activity )
45
	{
46
		_activity = activity;
47
	}
48
49
	@Override
50
	public void populateCache( ContactsCache cache )
51
	{
52
		Cursor cur;
53
54
		// set of contact ids that we have not yet added
57 by edam
cleanup; fixed some typos; updated TODO
55
		HashSet< Long > unadded_ids = new HashSet< Long >();
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
56
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
57
		// notes
58
		HashMap< Long, String > notes = new HashMap< Long, String >();
59
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
60
		// get all contacts
61
		cur = _activity.managedQuery( Contacts.People.CONTENT_URI,
62
			new String[] {
63
				Contacts.People._ID,
64
				Contacts.People.NAME,
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
65
				Contacts.People.NOTES,
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
66
			}, null, null, null );
67
		while( cur.moveToNext() ) {
68
			Long id = cur.getLong(
69
				cur.getColumnIndex( Contacts.People._ID ) );
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
70
			String name = cur.getString(
71
					cur.getColumnIndex( Contacts.People.NAME ) );
72
			String note = cur.getString(
73
					cur.getColumnIndex( Contacts.People.NOTES ) );
74
75
			// if we can, add a lookup for the contact id by name
76
			CacheIdentifier cache_identifier = CacheIdentifier.factory(
77
				CacheIdentifier.Type.NAME, name );
78
			if( cache_identifier != null ) {
79
				cache.addLookup( cache_identifier, id );
80
81
				// add any associated notes (this would get done at the end but,
82
				// since it is most common that contacts are identified by name,
83
				// it is worth doing a special case here
84
				cache.addAssociatedNote( id, note );
85
			}
86
			else
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
87
			{
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
88
				// record that a lookup for this contact's id still needs to be
89
				// added by some other means
90
				unadded_ids.add( id );
91
92
				// store this contact's notes, so that they can be added to the
93
				// cache at the end, after this contact has been added (by
94
				// whatever identifying means)
95
				if( note != null && note.length() > 0 )
96
					notes.put( id, note );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
97
			}
98
		}
99
100
		// get contact organisations, primary ones first
101
		cur = _activity.managedQuery( Contacts.Organizations.CONTENT_URI,
102
			new String[] {
103
				Contacts.Phones.PERSON_ID,
104
				Contacts.Organizations.COMPANY,
105
			}, null, null, Contacts.Organizations.ISPRIMARY + " DESC" );
106
		while( cur.moveToNext() ) {
107
			Long id = cur.getLong( cur.getColumnIndex(
108
				Contacts.Organizations.PERSON_ID ) );
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
109
			String organisation = cur.getString(
110
				cur.getColumnIndex( Contacts.Organizations.COMPANY ) );
111
112
			// if this is an organisation name for a contact for whom we have
113
			// not added a lookup, add a lookup for the contact id by
114
			// organisation
115
			if( unadded_ids.contains( id ) ) {
116
				CacheIdentifier cache_identifier = CacheIdentifier.factory(
117
					CacheIdentifier.Type.ORGANISATION, organisation );
118
				if( cache_identifier != null ) {
119
					cache.addLookup( cache_identifier, id );
57 by edam
cleanup; fixed some typos; updated TODO
120
					unadded_ids.remove( id );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
121
				}
122
			}
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
123
124
			// add associated data
125
			cache.addAssociatedOrganisation( id, organisation );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
126
		}
127
128
		// get all phone numbers, primary ones first
129
		cur = _activity.managedQuery( Contacts.Phones.CONTENT_URI,
130
			new String[] {
131
				Contacts.Phones.PERSON_ID,
132
				Contacts.Phones.NUMBER,
133
			}, null, null, Contacts.Phones.ISPRIMARY + " DESC" );
134
		while( cur.moveToNext() ) {
135
			Long id = cur.getLong(
136
				cur.getColumnIndex( Contacts.Phones.PERSON_ID ) );
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
137
			String number = cur.getString(
138
				cur.getColumnIndex( Contacts.Phones.NUMBER ) );
139
140
			// if this is a number for a contact for whom we have not
141
			// added a lookup, add a lookup for the contact id by phone
142
			// number
143
			if( unadded_ids.contains( id ) ) {
144
				CacheIdentifier cache_identifier = CacheIdentifier.factory(
145
					CacheIdentifier.Type.PRIMARY_NUMBER, number );
146
				if( cache_identifier != null ) {
147
					cache.addLookup( cache_identifier, id );
57 by edam
cleanup; fixed some typos; updated TODO
148
					unadded_ids.remove( id );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
149
				}
150
			}
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
151
152
			// add associated data
153
			cache.addAssociatedNumber( id, number );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
154
		}
155
156
		// now get all email addresses, primary ones first, and postal addresses
157
		cur = _activity.managedQuery( Contacts.ContactMethods.CONTENT_URI,
158
			new String[] {
159
				Contacts.ContactMethods.PERSON_ID,
160
				Contacts.ContactMethods.DATA,
161
				Contacts.ContactMethods.KIND,
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
162
			}, Contacts.ContactMethods.KIND + " IN( ?, ? )",
163
			new String[] {
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
164
				"" + Contacts.KIND_EMAIL,
165
				"" + Contacts.KIND_POSTAL,
166
			}, Contacts.ContactMethods.ISPRIMARY + " DESC" );
167
		while( cur.moveToNext() ) {
168
			Long id = cur.getLong(
169
				cur.getColumnIndex( Contacts.ContactMethods.PERSON_ID ) );
170
			int kind = cur.getInt(
171
				cur.getColumnIndex( Contacts.ContactMethods.KIND ) );
172
			if( kind == Contacts.KIND_EMAIL )
173
			{
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
174
				String email = cur.getString(
175
					cur.getColumnIndex( Contacts.ContactMethods.DATA ) );
176
177
				// if this is an email address for a contact for whom we have
178
				// not added a lookup, add a lookup for the contact id by email
179
				// address
180
				if( unadded_ids.contains( id ) ) {
181
					CacheIdentifier cache_identifier = CacheIdentifier.factory(
182
						CacheIdentifier.Type.PRIMARY_EMAIL, email );
183
					if( cache_identifier != null ) {
184
						cache.addLookup( cache_identifier, id );
57 by edam
cleanup; fixed some typos; updated TODO
185
						unadded_ids.remove( id );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
186
					}
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
187
				}
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
188
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
189
				// add associated data
190
				cache.addAssociatedEmail( id, email );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
191
			}
192
			else if( kind == Contacts.KIND_POSTAL )
193
			{
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
194
				String address = cur.getString(
195
					cur.getColumnIndex( Contacts.ContactMethods.DATA ) );
196
197
				// add associated data
198
				cache.addAssociatedAddress( id, address );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
199
			}
200
		}
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
201
202
		// finally, add the notes that we stored earlier (we have to add these
203
		// at the end because we can't be sure which piece of contact data will
204
		// cause the contact to be added to the cache
205
		Iterator< Long > i = notes.keySet().iterator();
206
		while( i.hasNext() ) {
207
			Long id = i.next();
208
			cache.addAssociatedNote( id, notes.get( id ) );
209
		}
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
210
	}
211
212
	@Override
213
	public void deleteContact( Long id )
214
	{
215
		Uri contact_uri =
216
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id );
217
		_activity.getContentResolver().delete( contact_uri, null, null );
218
	}
219
220
	@Override
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
221
	public Long addContact( String name ) throws ContactCreationException
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
222
	{
223
		ContentValues values = new ContentValues();
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
224
		if( name != null )
225
			values.put( Contacts.People.NAME, name );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
226
		Uri contact_uri = _activity.getContentResolver().insert(
227
			Contacts.People.CONTENT_URI, values );
228
		Long id = ContentUris.parseId( contact_uri );
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
229
		if( id == 0 )
230
			throw new ContactCreationException();
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
231
232
		// try to add them to the "My Contacts" group
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
233
		try {
234
			Contacts.People.addToMyContactsGroup(
235
				_activity.getContentResolver(), id );
236
		}
237
		catch( IllegalStateException e ) {
238
			// ignore any failure
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
239
		}
240
57 by edam
cleanup; fixed some typos; updated TODO
241
		return id;
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
242
	}
243
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
244
	private int convertTypeToBackendType( Class< ? > cls, int type )
245
		throws ContactCreationException
246
	{
247
		if( cls == Contacts.Phones.class )
248
		{
249
			switch( type )
250
			{
251
			case ContactData.TYPE_HOME:
252
				return Contacts.Phones.TYPE_HOME;
253
			case ContactData.TYPE_WORK:
254
				return Contacts.Phones.TYPE_WORK;
255
			case ContactData.TYPE_MOBILE:
256
				return Contacts.Phones.TYPE_MOBILE;
257
			case ContactData.TYPE_FAX_HOME:
258
				return Contacts.Phones.TYPE_FAX_HOME;
259
			case ContactData.TYPE_FAX_WORK:
260
				return Contacts.Phones.TYPE_FAX_WORK;
261
			case ContactData.TYPE_PAGER:
262
				return Contacts.Phones.TYPE_PAGER;
263
			}
264
		}
265
		else if( cls == Contacts.ContactMethods.class )
266
		{
267
			switch( type )
268
			{
269
			case ContactData.TYPE_HOME:
270
				return Contacts.ContactMethods.TYPE_HOME;
271
			case ContactData.TYPE_WORK:
272
				return Contacts.ContactMethods.TYPE_WORK;
273
			}
274
		}
275
276
		// still here?
277
		throw new ContactCreationException();
278
	}
279
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
280
	@Override
281
	public void addContactPhone( Long id, String number,
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
282
		ContactData.PreferredDetail data ) throws ContactCreationException
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
283
	{
284
		Uri contact_phones_uri = Uri.withAppendedPath(
285
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
286
			Contacts.People.Phones.CONTENT_DIRECTORY );
287
288
		ContentValues values = new ContentValues();
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
289
		values.put( Contacts.Phones.TYPE,
290
			convertTypeToBackendType( Contacts.Phones.class,
291
				data.getType() ) );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
292
		values.put( Contacts.Phones.NUMBER, number );
293
		if( data.isPreferred() )
294
			values.put( Contacts.Phones.ISPRIMARY, 1 );
295
296
		_activity.getContentResolver().insert( contact_phones_uri, values );
297
	}
298
299
	@Override
300
	public void addContactEmail( Long id, String email,
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
301
		ContactData.PreferredDetail data ) throws ContactCreationException
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
302
	{
303
		Uri contact_contact_methods_uri = Uri.withAppendedPath(
304
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
305
			Contacts.People.ContactMethods.CONTENT_DIRECTORY );
306
307
		ContentValues values = new ContentValues();
308
		values.put( Contacts.ContactMethods.KIND, Contacts.KIND_EMAIL );
309
		values.put( Contacts.ContactMethods.DATA, email );
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
310
		values.put( Contacts.ContactMethods.TYPE,
311
			convertTypeToBackendType( Contacts.ContactMethods.class,
312
				data.getType() ) );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
313
		if( data.isPreferred() )
314
			values.put( Contacts.ContactMethods.ISPRIMARY, 1 );
315
316
		_activity.getContentResolver().insert( contact_contact_methods_uri,
317
			values );
318
	}
319
320
	@Override
321
	public void addContactAddresses( Long id, String address,
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
322
		ContactData.TypeDetail data ) throws ContactCreationException
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
323
	{
324
		Uri contact_contact_methods_uri = Uri.withAppendedPath(
325
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
326
			Contacts.People.ContactMethods.CONTENT_DIRECTORY );
327
328
		ContentValues values = new ContentValues();
329
		values.put( Contacts.ContactMethods.KIND, Contacts.KIND_POSTAL );
330
		values.put( Contacts.ContactMethods.DATA, address );
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
331
		values.put( Contacts.ContactMethods.TYPE,
332
			convertTypeToBackendType( Contacts.ContactMethods.class,
333
				data.getType() ) );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
334
335
		_activity.getContentResolver().insert( contact_contact_methods_uri,
336
			values );
337
	}
338
339
	@Override
340
	public void addContactOrganisation( Long id, String organisation,
61 by edam
made contacts backend throw exceptions when it can't create contacts on the device; specified Locale in lower/upper case conversions; stripped old Contacts types from Importer (and replaced with our own types, which are now converted in the backend; switched using Integer() constructors to Integer.valueOf(); rewrote the main importer routine a bit
341
		ContactData.ExtraDetail data ) throws ContactCreationException
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
342
	{
343
		ContentValues values = new ContentValues();
344
		values.put( Contacts.Organizations.PERSON_ID, id );
345
		values.put( Contacts.Organizations.COMPANY, organisation );
346
		values.put( Contacts.ContactMethods.TYPE,
347
			Contacts.OrganizationColumns.TYPE_WORK );
348
		if( data.getExtra() != null )
349
			values.put( Contacts.Organizations.TITLE, data.getExtra() );
350
351
		_activity.getContentResolver().insert(
352
			Contacts.Organizations.CONTENT_URI, values );
353
	}
354
65 by edam
added support for notes; rewrote backends so that all normalising of data is now done within the contacts cache; made the vCard unescape routine slightly more acceptant of non-standard escaped characters
355
	@Override
356
	public void addContactNote( Long id, String note )
357
		throws ContactCreationException
358
	{
359
		ContentValues values = new ContentValues();
360
		values.put( Contacts.People.NOTES, note );
361
		_activity.getContentResolver().update(
362
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
363
			values, null, null );
364
	}
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
365
366
}