/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts
6 by edam
- added GPL header comments to all files
1
/*
2
 * Importer.java
3
 *
4
 * Copyright (C) 2009 Tim Marston <edam@waxworlds.org>
5
 *
6
 * This file is part of the Import Contacts program (hereafter referred
7
 * to as "this program"). For more information, see
8
 * http://www.waxworlds.org/edam/software/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
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
24
package org.waxworlds.edam.importcontacts;
1 by edam
Initial import
25
26
import java.util.HashMap;
27
import java.util.HashSet;
28
import java.util.Iterator;
29
import java.util.Set;
30
import java.util.regex.Matcher;
31
import java.util.regex.Pattern;
32
33
import android.content.ContentUris;
34
import android.content.ContentValues;
35
import android.content.SharedPreferences;
36
import android.database.Cursor;
37
import android.net.Uri;
38
import android.os.Message;
39
import android.provider.Contacts;
40
37 by edam
- updated TODO and NEWS
41
1 by edam
Initial import
42
public class Importer extends Thread
43
{
3 by edam
- added "all done" message
44
	public final static int ACTION_ABORT = 1;
45
	public final static int ACTION_ALLDONE = 2;
1 by edam
Initial import
46
47
	public final static int RESPONSE_NEGATIVE = 0;
48
	public final static int RESPONSE_POSITIVE = 1;
49
50
	public final static int RESPONSEEXTRA_NONE = 0;
51
	public final static int RESPONSEEXTRA_ALWAYS = 1;
52
53
	private Doit _doit;
54
	private int _response;
55
	private int _responseExtra;
56
	private HashMap< String, Long > _contacts;
57
	private HashMap< Long, HashSet< String > > _contactNumbers;
58
	private HashMap< Long, HashSet< String > > _contactEmails;
37 by edam
- updated TODO and NEWS
59
	private HashMap< Long, HashSet< String > > _contactAddresses;
1 by edam
Initial import
60
	private int _mergeSetting;
61
	private int _lastMergeDecision;
62
	private boolean _abort = false;
3 by edam
- added "all done" message
63
	private boolean _isFinished = false;
1 by edam
Initial import
64
65
	public class ContactData
66
	{
67
		class PhoneData
68
		{
69
			public String _number;
70
			public int _type;
71
			public boolean _isPreferred;
72
73
			public PhoneData( String number, int type, boolean isPreferred ) {
74
				_number = number;
75
				_type = type;
76
				_isPreferred = isPreferred;
77
			}
78
79
			public String getNumber() {
80
				return _number;
81
			}
82
83
			public int getType() {
84
				return _type;
85
			}
86
87
			public boolean isPreferred() {
88
				return _isPreferred;
89
			}
90
		}
91
92
		class EmailData
93
		{
94
			private String _email;
95
			public int _type;
96
			private boolean _isPreferred;
97
98
			public EmailData( String email, int type, boolean isPreferred ) {
99
				_email = email;
100
				_type = type;
101
				_isPreferred = isPreferred;
102
			}
103
104
			public String getAddress() {
105
				return _email;
106
			}
107
108
			public int getType() {
109
				return _type;
110
			}
111
112
			public boolean isPreferred() {
113
				return _isPreferred;
114
			}
115
		}
116
37 by edam
- updated TODO and NEWS
117
		class AddressData
118
		{
119
			private String _address;
120
			public int _type;
121
122
			public AddressData( String address, int type ) {
123
				_address = address;
124
				_type = type;
125
			}
126
127
			public String getAddress() {
128
				return _address;
129
			}
130
131
			public int getType() {
132
				return _type;
133
			}
134
		}
135
1 by edam
Initial import
136
		public String _name = null;
137
		public HashMap< String, PhoneData > _phones = null;
138
		public HashMap< String, EmailData > _emails = null;
37 by edam
- updated TODO and NEWS
139
		public HashMap< String, AddressData > _addresses = null;
1 by edam
Initial import
140
141
		protected void setName( String name )
142
		{
143
			_name = name;
144
		}
145
146
		public String getName()
147
		{
148
			return _name;
149
		}
150
151
		protected void addPhone( String number, int type, boolean isPreferred )
152
		{
153
			if( _phones == null ) _phones = new HashMap< String, PhoneData >();
154
			if( !_phones.containsKey( number ) )
155
				_phones.put( number,
36 by edam
- formatting: removed some double-indents on overrunning lines
156
					new PhoneData( number, type, isPreferred ) );
1 by edam
Initial import
157
		}
158
159
		protected void addEmail( String email, int type, boolean isPreferred )
160
		{
161
			if( _emails == null ) _emails = new HashMap< String, EmailData >();
162
			if( !_emails.containsKey( email ) )
163
				_emails.put( email, new EmailData( email, type, isPreferred ) );
164
		}
37 by edam
- updated TODO and NEWS
165
166
		protected void addAddress( String address, int type )
167
		{
168
			if( _addresses == null ) _addresses =
169
				new HashMap< String, AddressData >();
170
			if( !_addresses.containsKey( address ) )
171
				_addresses.put( address, new AddressData( address, type ) );
172
		}
1 by edam
Initial import
173
	}
174
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
175
	@SuppressWarnings("serial")
1 by edam
Initial import
176
	protected class AbortImportException extends Exception { };
177
178
	public Importer( Doit doit )
179
	{
180
		_doit = doit;
181
182
		SharedPreferences prefs = getSharedPreferences();
9 by edam
- added scroll view to all layouts
183
		_mergeSetting = prefs.getInt( "merge_setting", Doit.ACTION_PROMPT );
1 by edam
Initial import
184
	}
185
186
	@Override
187
	public void run()
188
	{
189
		try
190
		{
191
			// cache current contact names
192
			buildContactsCache();
193
194
			// do the import
195
			onImport();
196
197
			// done!
3 by edam
- added "all done" message
198
			finish( ACTION_ALLDONE );
1 by edam
Initial import
199
		}
200
		catch( AbortImportException e )
201
		{}
3 by edam
- added "all done" message
202
203
		// flag as finished to prevent interrupts
204
		setIsFinished();
205
	}
206
207
	synchronized private void setIsFinished()
208
	{
209
		_isFinished = true;
1 by edam
Initial import
210
	}
211
212
	protected void onImport() throws AbortImportException
213
	{
214
	}
215
216
	public void wake()
217
	{
218
		wake( 0, RESPONSEEXTRA_NONE );
219
	}
220
221
	public void wake( int response )
222
	{
223
		wake( response, RESPONSEEXTRA_NONE );
224
	}
225
226
	synchronized public void wake( int response, int responseExtra )
227
	{
228
		_response = response;
229
		_responseExtra = responseExtra;
230
		notify();
231
	}
232
3 by edam
- added "all done" message
233
	synchronized public boolean setAbort()
1 by edam
Initial import
234
	{
3 by edam
- added "all done" message
235
		if( !_isFinished && !_abort ) {
236
			_abort = true;
237
			notify();
238
			return true;
239
		}
240
		return false;
1 by edam
Initial import
241
	}
242
243
	protected SharedPreferences getSharedPreferences()
244
	{
245
		return _doit.getSharedPreferences();
246
	}
247
248
	protected void showError( int res ) throws AbortImportException
249
	{
250
		showError( _doit.getText( res ).toString() );
251
	}
252
253
	synchronized protected void showError( String message )
254
			throws AbortImportException
255
	{
256
		checkAbort();
257
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
258
			_doit._handler, Doit.MESSAGE_ERROR, message ) );
1 by edam
Initial import
259
		try {
260
			wait();
261
		}
262
		catch( InterruptedException e ) { }
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
263
3 by edam
- added "all done" message
264
		// no need to check if an abortion happened during the wait, we are
265
		// about to finish anyway!
266
		finish( ACTION_ABORT );
1 by edam
Initial import
267
	}
268
269
	protected void showFatalError( int res ) throws AbortImportException
270
	{
271
		showFatalError( _doit.getText( res ).toString() );
272
	}
273
274
	synchronized protected void showFatalError( String message )
275
			throws AbortImportException
276
	{
277
		checkAbort();
278
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
279
			_doit._handler, Doit.MESSAGE_ERROR, message ) );
1 by edam
Initial import
280
		try {
281
			wait();
282
		}
283
		catch( InterruptedException e ) { }
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
284
3 by edam
- added "all done" message
285
		// no need to check if an abortion happened during the wait, we are
286
		// about to finish anyway!
287
		finish( ACTION_ABORT );
1 by edam
Initial import
288
	}
289
290
	protected boolean showContinue( int res ) throws AbortImportException
291
	{
292
		return showContinue( _doit.getText( res ).toString() );
293
	}
294
295
	synchronized protected boolean showContinue( String message )
296
			throws AbortImportException
297
	{
298
		checkAbort();
299
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
300
			_doit._handler, Doit.MESSAGE_CONTINUEORABORT, message ) );
1 by edam
Initial import
301
		try {
302
			wait();
303
		}
304
		catch( InterruptedException e ) { }
3 by edam
- added "all done" message
305
306
		// check if an abortion happened during the wait
307
		checkAbort();
308
1 by edam
Initial import
309
		return _response == RESPONSE_POSITIVE;
310
	}
311
312
	protected void setProgressMessage( int res ) throws AbortImportException
313
	{
314
		checkAbort();
315
		_doit._handler.sendMessage( Message.obtain( _doit._handler,
36 by edam
- formatting: removed some double-indents on overrunning lines
316
			Doit.MESSAGE_SETPROGRESSMESSAGE, getText( res ) ) );
1 by edam
Initial import
317
	}
318
319
	protected void setProgressMax( int maxProgress )
320
			throws AbortImportException
321
	{
322
		checkAbort();
323
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
324
			_doit._handler, Doit.MESSAGE_SETMAXPROGRESS,
325
			new Integer( maxProgress ) ) );
1 by edam
Initial import
326
	}
327
328
	protected void setTmpProgress( int tmpProgress ) throws AbortImportException
329
	{
330
		checkAbort();
331
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
332
			_doit._handler, Doit.MESSAGE_SETTMPPROGRESS,
333
			new Integer( tmpProgress ) ) );
1 by edam
Initial import
334
	}
335
336
	protected void setProgress( int progress ) throws AbortImportException
337
	{
338
		checkAbort();
339
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
340
			_doit._handler, Doit.MESSAGE_SETPROGRESS,
341
			new Integer( progress ) ) );
1 by edam
Initial import
342
	}
343
3 by edam
- added "all done" message
344
	protected void finish( int action ) throws AbortImportException
1 by edam
Initial import
345
	{
3 by edam
- added "all done" message
346
		// update UI to reflect action
347
		int message;
348
		switch( action )
349
		{
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
350
		case ACTION_ALLDONE:	message = Doit.MESSAGE_ALLDONE; break;
3 by edam
- added "all done" message
351
		default:	// fall through
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
352
		case ACTION_ABORT:		message = Doit.MESSAGE_ABORT; break;
3 by edam
- added "all done" message
353
		}
354
		_doit._handler.sendEmptyMessage( message );
1 by edam
Initial import
355
3 by edam
- added "all done" message
356
		// stop
357
		throw new AbortImportException();
1 by edam
Initial import
358
	}
359
360
	protected CharSequence getText( int res )
361
	{
362
		return _doit.getText( res );
363
	}
364
365
	protected boolean isImportRequired( String name )
366
			throws AbortImportException
367
	{
368
		checkAbort();
369
		return isImportRequired( name, _mergeSetting );
370
	}
371
3 by edam
- added "all done" message
372
	synchronized private boolean isImportRequired( String name,
373
			int mergeSetting ) throws AbortImportException
1 by edam
Initial import
374
	{
375
		_lastMergeDecision = mergeSetting;
376
377
		// handle special cases
378
		switch( mergeSetting )
379
		{
9 by edam
- added scroll view to all layouts
380
		case Doit.ACTION_KEEP:
1 by edam
Initial import
381
			// if we keep contacts on duplicate, we better check for one
382
			return !_contacts.containsKey( name );
383
9 by edam
- added scroll view to all layouts
384
		case Doit.ACTION_PROMPT:
1 by edam
Initial import
385
			// if we are prompting on duplicate, we better check for one
386
			if( !_contacts.containsKey( name ) )
387
				return true;
388
389
			// ok, it exists, so do prompt
390
			_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
391
				_doit._handler, Doit.MESSAGE_MERGEPROMPT, name ) );
1 by edam
Initial import
392
			try {
393
				wait();
394
			}
395
			catch( InterruptedException e ) { }
396
3 by edam
- added "all done" message
397
			// check if an abortion happened during the wait
398
			checkAbort();
399
1 by edam
Initial import
400
			// if "always" was selected, make choice permenant
401
			if( _responseExtra == RESPONSEEXTRA_ALWAYS )
402
				_mergeSetting = _response;
403
36 by edam
- formatting: removed some double-indents on overrunning lines
404
			// recurse, with our new merge setting
1 by edam
Initial import
405
			return isImportRequired( name, _response );
406
		}
407
408
		// for all other cases (either overwriting or merging) we will need the
409
		// imported data
410
		return true;
411
	}
412
413
	protected void skipContact() throws AbortImportException
414
	{
415
		checkAbort();
3 by edam
- added "all done" message
416
		_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTSKIPPED );
1 by edam
Initial import
417
	}
418
419
	protected void importContact( ContactData contact )
420
			throws AbortImportException
421
	{
422
		checkAbort();
423
3 by edam
- added "all done" message
424
//		if( !showContinue( "====[ IMPORTING ]====\n: " + contact._name ) )
425
//			finish( ACTION_ABORT );
1 by edam
Initial import
426
427
		ContentValues values = new ContentValues();
428
		boolean uiInformed = false;
429
430
		// does contact exist already?
431
		Uri contactUri = null;
432
		Long id;
433
		if( ( id = (Long)_contacts.get( contact._name ) ) != null )
434
		{
435
			// should we skip this import altogether?
9 by edam
- added scroll view to all layouts
436
			if( _lastMergeDecision == Doit.ACTION_KEEP ) return;
1 by edam
Initial import
437
438
			// get contact's URI
439
			contactUri = ContentUris.withAppendedId(
36 by edam
- formatting: removed some double-indents on overrunning lines
440
				Contacts.People.CONTENT_URI, id );
1 by edam
Initial import
441
442
			// should we destroy the existing contact before importing?
9 by edam
- added scroll view to all layouts
443
			if( _lastMergeDecision == Doit.ACTION_OVERWRITE ) {
1 by edam
Initial import
444
				_doit.getContentResolver().delete( contactUri, null, null );
445
				contactUri = null;
446
36 by edam
- formatting: removed some double-indents on overrunning lines
447
				// update the UI
448
				_doit._handler.sendEmptyMessage(
449
						Doit.MESSAGE_CONTACTOVERWRITTEN );
1 by edam
Initial import
450
				uiInformed = true;
451
452
				// update cache
453
				_contacts.remove( contact._name );
454
			}
455
		}
456
457
		// if we don't have a contact URI it is because the contact never
458
		// existed or because we deleted it
459
		if( contactUri == null )
460
		{
461
			// create a new contact
462
			values.put( Contacts.People.NAME, contact._name );
463
			contactUri = _doit.getContentResolver().insert(
36 by edam
- formatting: removed some double-indents on overrunning lines
464
				Contacts.People.CONTENT_URI, values );
1 by edam
Initial import
465
			id = ContentUris.parseId( contactUri );
466
			if( id <= 0 ) return;	// shouldn't happen!
467
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
468
			// try to add them to the "My Contacts" group
469
			try {
470
				Contacts.People.addToMyContactsGroup(
12 by edam
- bugfix: add contacts to the "my contacts" group didn't actually work on a real device. So we're doing it a different way.
471
					_doit.getContentResolver(), id );
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
472
			}
36 by edam
- formatting: removed some double-indents on overrunning lines
473
			catch( IllegalStateException e ) {
474
				// ignore any failure
475
			}
1 by edam
Initial import
476
477
			// update cache
478
			_contacts.put( contact._name, id );
479
480
			// update UI
481
			if( !uiInformed ) {
3 by edam
- added "all done" message
482
				_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTCREATED );
1 by edam
Initial import
483
				uiInformed = true;
484
			}
485
		}
486
487
		// update UI
488
		if( !uiInformed )
3 by edam
- added "all done" message
489
			_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTMERGED );
1 by edam
Initial import
490
491
		// import contact parts
492
		if( contact._phones != null )
493
			importContactPhones( contactUri, contact._phones );
494
		if( contact._emails != null )
495
			importContactEmails( contactUri, contact._emails );
37 by edam
- updated TODO and NEWS
496
		if( contact._addresses != null )
497
			importContactAddresses( contactUri, contact._addresses );
1 by edam
Initial import
498
	}
499
500
	private void importContactPhones( Uri contactUri,
501
			HashMap< String, ContactData.PhoneData > phones )
502
	{
503
		Long contactId = ContentUris.parseId( contactUri );
504
		Uri contactPhonesUri = Uri.withAppendedPath( contactUri,
505
				Contacts.People.Phones.CONTENT_DIRECTORY );
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
506
		Set< String > phonesKeys = phones.keySet();
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
507
508
		// add phone numbers
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
509
		Iterator< String > i = phonesKeys.iterator();
1 by edam
Initial import
510
		while( i.hasNext() ) {
511
			ContactData.PhoneData phone = phones.get( i.next() );
512
513
			// we don't want to add this number if it's crap, or it already
514
			// exists (which would cause a duplicate to be created). We don't
515
			// take in to account the type when checking for duplicates. This is
516
			// intentional: types aren't really very reliable. We assume that
517
			// if the number exists at all, it doesn't need importing. Because
518
			// of this, we also can't update the cache (which we don't need to
519
			// anyway, so it's not a problem).
520
			String number = sanitisePhoneNumber( phone._number );
521
			if( number == null ) continue;
37 by edam
- updated TODO and NEWS
522
			HashSet< String > cache = _contactNumbers.get( contactId );
523
			if( cache != null && cache.contains( number ) ) continue;
1 by edam
Initial import
524
525
			// add phone number
526
			ContentValues values = new ContentValues();
527
			values.put( Contacts.Phones.TYPE, phone._type );
528
			values.put( Contacts.Phones.NUMBER, phone._number );
529
			if( phone._isPreferred ) values.put( Contacts.Phones.ISPRIMARY, 1 );
530
			_doit.getContentResolver().insert( contactPhonesUri, values );
37 by edam
- updated TODO and NEWS
531
532
			// and add this address to the cache to prevent a addition of
533
			// duplicate date from another file
534
			if( cache == null ) {
535
				cache = new HashSet< String >();
536
				_contactNumbers.put( contactId, cache );
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
537
			}
37 by edam
- updated TODO and NEWS
538
			cache.add( number );
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
539
		}
1 by edam
Initial import
540
	}
541
542
	private void importContactEmails( Uri contactUri,
543
			HashMap< String, ContactData.EmailData > emails )
544
	{
545
		Long contactId = ContentUris.parseId( contactUri );
546
		Uri contactContactMethodsUri = Uri.withAppendedPath( contactUri,
547
				Contacts.People.ContactMethods.CONTENT_DIRECTORY );
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
548
		Set< String > emailsKeys = emails.keySet();
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
549
550
		// add email addresses
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
551
		Iterator< String > i = emailsKeys.iterator();
1 by edam
Initial import
552
		while( i.hasNext() ) {
553
			ContactData.EmailData email = emails.get( i.next() );
554
37 by edam
- updated TODO and NEWS
555
			// we don't want to add this email address if it exists already or
556
			// we would introduce duplicates.
1 by edam
Initial import
557
			String address = sanitiseEmailAddress( email.getAddress() );
558
			if( address == null ) continue;
37 by edam
- updated TODO and NEWS
559
			HashSet< String > cache = _contactEmails.get( contactId );
560
			if( cache != null && cache.contains( address ) ) continue;
1 by edam
Initial import
561
562
			// add phone number
563
			ContentValues values = new ContentValues();
564
			values.put( Contacts.ContactMethods.KIND, Contacts.KIND_EMAIL );
565
			values.put( Contacts.ContactMethods.DATA, email.getAddress() );
566
			values.put( Contacts.ContactMethods.TYPE, email.getType() );
567
			if( email.isPreferred() )
568
				values.put( Contacts.ContactMethods.ISPRIMARY, 1 );
569
			_doit.getContentResolver().insert( contactContactMethodsUri,
36 by edam
- formatting: removed some double-indents on overrunning lines
570
				values );
37 by edam
- updated TODO and NEWS
571
572
			// and add this address to the cache to prevent a addition of
573
			// duplicate date from another file
574
			if( cache == null ) {
575
				cache = new HashSet< String >();
576
				_contactEmails.put( contactId, cache );
577
			}
578
			cache.add( address );
1 by edam
Initial import
579
		}
37 by edam
- updated TODO and NEWS
580
	}
581
582
	private void importContactAddresses( Uri contactUri,
583
		HashMap< String, ContactData.AddressData > addresses )
584
	{
585
		Long contactId = ContentUris.parseId( contactUri );
586
		Uri contactContactMethodsUri = Uri.withAppendedPath( contactUri,
587
				Contacts.People.ContactMethods.CONTENT_DIRECTORY );
588
		Set< String > addressesKeys = addresses.keySet();
589
590
		// add addresses
591
		Iterator< String > i = addressesKeys.iterator();
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
592
		while( i.hasNext() ) {
37 by edam
- updated TODO and NEWS
593
			ContactData.AddressData address = addresses.get( i.next() );
594
595
			// we don't want to add this address if it exists already or we
596
			// would introduce duplicates
597
			if( address == null ) continue;
598
			HashSet< String > cache = _contactAddresses.get( contactId );
599
			if( cache != null && cache.contains( address.getAddress() ) )
600
				continue;
601
602
			// add postal address
603
			ContentValues values = new ContentValues();
604
			values.put( Contacts.ContactMethods.KIND, Contacts.KIND_POSTAL );
605
			values.put( Contacts.ContactMethods.DATA, address.getAddress() );
606
			values.put( Contacts.ContactMethods.TYPE, address.getType() );
607
			_doit.getContentResolver().insert( contactContactMethodsUri,
608
				values );
609
610
			// and add this address to the cache to prevent a addition of
611
			// duplicate date from another file
612
			if( cache == null ) {
613
				cache = new HashSet< String >();
614
				_contactAddresses.put( contactId, cache );
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
615
			}
37 by edam
- updated TODO and NEWS
616
			cache.add( address.getAddress() );
7 by edam
- new contact's phone numebrs and email addresses are added to the caches after those contacts are updated to account for the situation where the same contact is imported again from another file (or the contact exists twice in the same file!?)
617
		}
1 by edam
Initial import
618
	}
619
3 by edam
- added "all done" message
620
	synchronized protected void checkAbort() throws AbortImportException
1 by edam
Initial import
621
	{
622
		if( _abort ) {
623
			// stop
624
			throw new AbortImportException();
625
		}
626
	}
627
628
	private void buildContactsCache() throws AbortImportException
629
	{
630
		// update UI
631
		setProgressMessage( R.string.doit_caching );
632
633
		String[] cols;
634
		Cursor cur;
635
636
		// init contacts caches
637
		_contacts = new HashMap< String, Long >();
638
		_contactNumbers = new HashMap< Long, HashSet< String > >();
639
		_contactEmails = new HashMap< Long, HashSet< String > >();
37 by edam
- updated TODO and NEWS
640
		_contactAddresses = new HashMap< Long, HashSet< String > >();
1 by edam
Initial import
641
642
		// query and store map of contact names to ids
643
		cols = new String[] { Contacts.People._ID, Contacts.People.NAME };
644
		cur = _doit.managedQuery( Contacts.People.CONTENT_URI,
36 by edam
- formatting: removed some double-indents on overrunning lines
645
			cols, null, null, null);
1 by edam
Initial import
646
		if( cur.moveToFirst() ) {
647
			int idCol = cur.getColumnIndex( Contacts.People._ID );
648
			int nameCol = cur.getColumnIndex( Contacts.People.NAME );
649
			do {
650
				_contacts.put( cur.getString( nameCol ), cur.getLong( idCol ) );
651
			} while( cur.moveToNext() );
652
		}
653
654
		// query and store map of contact ids to sets of phone numbers
655
		cols = new String[] { Contacts.Phones.PERSON_ID,
656
				Contacts.Phones.NUMBER };
657
		cur = _doit.managedQuery( Contacts.Phones.CONTENT_URI,
36 by edam
- formatting: removed some double-indents on overrunning lines
658
			cols, null, null, null);
1 by edam
Initial import
659
		if( cur.moveToFirst() ) {
660
			int personIdCol = cur.getColumnIndex( Contacts.Phones.PERSON_ID );
661
			int numberCol = cur.getColumnIndex( Contacts.Phones.NUMBER );
662
			do {
663
				Long id = cur.getLong( personIdCol );
664
				String number = sanitisePhoneNumber(
665
						cur.getString( numberCol ) );
666
				if( number != null ) {
667
					HashSet< String > numbers = _contactNumbers.get( id );
668
					if( numbers == null ) {
37 by edam
- updated TODO and NEWS
669
						numbers = new HashSet< String >();
670
						_contactNumbers.put( id, numbers );
1 by edam
Initial import
671
					}
672
					numbers.add( number );
673
				}
674
			} while( cur.moveToNext() );
675
		}
676
677
		// query and store map of contact ids to sets of email addresses
678
		cols = new String[] { Contacts.ContactMethods.PERSON_ID,
679
				Contacts.ContactMethods.DATA };
680
		cur = _doit.managedQuery( Contacts.ContactMethods.CONTENT_URI,
681
				cols, Contacts.ContactMethods.KIND + " = ?",
682
				new String[] { "" + Contacts.KIND_EMAIL }, null );
683
		if( cur.moveToFirst() ) {
684
			int personIdCol = cur.getColumnIndex(
36 by edam
- formatting: removed some double-indents on overrunning lines
685
				Contacts.ContactMethods.PERSON_ID );
1 by edam
Initial import
686
			int addressCol = cur.getColumnIndex(
36 by edam
- formatting: removed some double-indents on overrunning lines
687
				Contacts.ContactMethods.DATA );
1 by edam
Initial import
688
			do {
689
				Long id = cur.getLong( personIdCol );
690
				String address = sanitiseEmailAddress(
36 by edam
- formatting: removed some double-indents on overrunning lines
691
					cur.getString( addressCol ) );
1 by edam
Initial import
692
				if( address != null ) {
693
					HashSet< String > addresses = _contactEmails.get( id );
694
					if( addresses == null ) {
37 by edam
- updated TODO and NEWS
695
						addresses = new HashSet< String >();
696
						_contactEmails.put( id, addresses );
697
					}
698
					addresses.add( address );
699
				}
700
			} while( cur.moveToNext() );
701
		}
702
703
		// query and store map of contact ids to sets of postal addresses
704
		cols = new String[] { Contacts.ContactMethods.PERSON_ID,
705
			Contacts.ContactMethods.DATA };
706
		cur = _doit.managedQuery( Contacts.ContactMethods.CONTENT_URI,
707
			cols, Contacts.ContactMethods.KIND + " = ?",
708
			new String[] { "" + Contacts.KIND_POSTAL }, null );
709
		if( cur.moveToFirst() ) {
710
			int personIdCol = cur.getColumnIndex(
711
				Contacts.ContactMethods.PERSON_ID );
712
			int addressCol = cur.getColumnIndex(
713
				Contacts.ContactMethods.DATA );
714
			do {
715
				Long id = cur.getLong( personIdCol );
716
				String address = cur.getString( addressCol );
717
				if( address != null ) {
718
					HashSet< String > addresses = _contactAddresses.get( id );
719
					if( addresses == null ) {
720
						addresses = new HashSet< String >();
721
						_contactAddresses.put( id, addresses );
1 by edam
Initial import
722
					}
723
					addresses.add( address );
724
				}
725
			} while( cur.moveToNext() );
726
		}
727
	}
728
729
	private String sanitisePhoneNumber( String number )
730
	{
731
		number = number.replaceAll( "[-\\(\\) ]", "" );
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
732
		Pattern p = Pattern.compile( "^[\\+0-9#*]+" );
1 by edam
Initial import
733
		Matcher m = p.matcher( number );
734
		if( m.lookingAt() ) return m.group( 0 );
735
		return null;
736
	}
737
738
	private String sanitiseEmailAddress( String address )
739
	{
740
		address = address.trim();
741
		Pattern p = Pattern.compile(
36 by edam
- formatting: removed some double-indents on overrunning lines
742
			"^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
1 by edam
Initial import
743
		Matcher m = p.matcher( address );
744
		if( m.matches() ) {
745
			String[] bits = address.split( "@" );
746
			return bits[ 0 ] + "@" + bits[ 1 ].toLowerCase();
747
		}
748
		return null;
749
	}
750
}