/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
 *
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
4
 * Copyright (C) 2009 to 2011 Tim Marston <edam@waxworlds.org>
6 by edam
- added GPL header comments to all files
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.Iterator;
28
import java.util.Set;
29
import java.util.regex.Matcher;
30
import java.util.regex.Pattern;
31
32
import android.content.ContentUris;
33
import android.content.ContentValues;
34
import android.content.SharedPreferences;
35
import android.net.Uri;
36
import android.os.Message;
37
import android.provider.Contacts;
38
37 by edam
- updated TODO and NEWS
39
1 by edam
Initial import
40
public class Importer extends Thread
41
{
3 by edam
- added "all done" message
42
	public final static int ACTION_ABORT = 1;
43
	public final static int ACTION_ALLDONE = 2;
1 by edam
Initial import
44
45
	public final static int RESPONSE_NEGATIVE = 0;
46
	public final static int RESPONSE_POSITIVE = 1;
47
48
	public final static int RESPONSEEXTRA_NONE = 0;
49
	public final static int RESPONSEEXTRA_ALWAYS = 1;
50
51
	private Doit _doit;
52
	private int _response;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
53
	private int _response_extra;
54
	private int _merge_setting;
55
	private int _last_merge_decision;
1 by edam
Initial import
56
	private boolean _abort = false;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
57
	private boolean _is_finished = false;
39 by edam
- pulled contacts cache out in to seperate class
58
	private ContactsCache _contactsCache = null;
1 by edam
Initial import
59
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
60
	@SuppressWarnings("serial")
61
	protected class ContactNeedsMoreInfoException extends Exception
62
	{
63
	}
64
65
	/**
66
	 * Data about a contact
67
	 */
1 by edam
Initial import
68
	public class ContactData
69
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
70
		class TypeDetail
71
		{
72
			protected int _type;
73
74
			public TypeDetail( int type )
75
			{
76
				_type = type;
77
			}
78
79
			public int getType()
80
			{
81
				return _type;
82
			}
83
		}
84
85
		class PreferredDetail extends TypeDetail
86
		{
87
			protected boolean _is_preferred;
88
89
			public PreferredDetail( int type, boolean is_preferred )
90
			{
91
				super( type );
92
				_is_preferred = is_preferred;
93
			}
94
95
			public boolean isPreferred()
96
			{
97
				return _is_preferred;
98
			}
99
		}
100
101
		class ExtraDetail extends PreferredDetail
102
		{
103
			protected String _extra;
104
105
			public ExtraDetail( int type, boolean is_preferred, String extra )
106
			{
107
				super( type, is_preferred );
108
109
				if( extra != null ) extra = extra.trim();
110
				_extra = extra;
111
			}
112
113
			public String getExtra()
114
			{
115
				return _extra;
116
			}
117
118
			public void setExtra( String extra )
119
			{
120
				if( extra != null ) extra = extra.trim();
121
				_extra = extra;
122
			}
123
		}
124
125
		protected String _name = null;
126
		protected String _primary_organisation = null;
127
		protected boolean _primary_organisation_is_preferred = false;
128
		protected String _primary_number = null;
129
		protected boolean _primary_number_is_preferred = false;
130
		protected String _primary_email = null;
131
		protected boolean _primary_email_is_preferred = false;
132
		protected HashMap< String, ExtraDetail > _organisations = null;
133
		protected HashMap< String, PreferredDetail > _numbers = null;
134
		protected HashMap< String, PreferredDetail > _emails = null;
135
		protected HashMap< String, TypeDetail > _addresses = null;
1 by edam
Initial import
136
137
		protected void setName( String name )
138
		{
139
			_name = name;
140
		}
141
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
142
		public boolean hasName()
143
		{
144
			return _name != null;
145
		}
146
1 by edam
Initial import
147
		public String getName()
148
		{
149
			return _name;
150
		}
151
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
152
		protected void addOrganisation( String organisation, String title,
153
			boolean is_preferred )
154
		{
155
			organisation = organisation.trim();
156
			if( organisation.length() <= 0 )
157
			{
158
				// TODO: warn that an imported organisation is being ignored
159
				return;
160
			}
161
162
			if( title != null ) {
163
				title = title.trim();
164
				if( title.length() <= 0 ) title = null;
165
			}
166
167
			// add the organisation, as non-preferred (we prefer only one
168
			// organisation in finalise() after they're all imported)
169
			if( _organisations == null )
170
				_organisations = new HashMap< String, ExtraDetail >();
171
			if( !_organisations.containsKey( organisation ) )
172
				_organisations.put( organisation,
173
					new ExtraDetail( 0, false, title ) );
174
175
			// if this is the first organisation added, or it's a preferred
176
			// organisation and a previous organisation wasn't, then remember
177
			// that this is the "primary organisation".
178
			if( _primary_organisation == null ||
179
				( is_preferred && !_primary_organisation_is_preferred ) )
180
			{
181
				_primary_organisation = organisation;
182
				_primary_organisation_is_preferred = is_preferred;
183
			}
184
		}
185
186
		public boolean hasOrganisations()
187
		{
188
			return _organisations != null && _organisations.size() > 0;
189
		}
190
191
		public HashMap< String, ExtraDetail > getOrganisations()
192
		{
193
			return _organisations;
194
		}
195
196
		public boolean hasPrimaryOrganisation()
197
		{
198
			return _primary_organisation != null;
199
		}
200
201
		public String getPrimaryOrganisation()
202
		{
203
			return _primary_organisation;
204
		}
205
206
		protected void addNumber( String number, int type,
207
			boolean is_preferred )
208
		{
209
			number = sanitisePhoneNumber( number );
210
			if( number == null )
211
			{
212
				// TODO: warn that an imported phone number is being ignored
213
				return;
214
			}
215
216
			// add the number, as non-preferred (we prefer only one number
217
			// in finalise() after they're all imported)
218
			if( _numbers == null )
219
				_numbers = new HashMap< String, PreferredDetail >();
220
			if( !_numbers.containsKey( number ) )
221
				_numbers.put( number,
222
					new PreferredDetail( type, false ) );
223
224
			// if this is the first number added, or it's a preferred number
225
			// and a previous number wasn't, then remember that this is the
226
			// "primary number".
227
			if( _primary_number == null ||
228
				( is_preferred && !_primary_number_is_preferred ) )
229
			{
230
				_primary_number = number;
231
				_primary_number_is_preferred = is_preferred;
232
			}
233
		}
234
235
		public boolean hasNumbers()
236
		{
237
			return _numbers != null && _numbers.size() > 0;
238
		}
239
240
		public HashMap< String, PreferredDetail > getNumbers()
241
		{
242
			return _numbers;
243
		}
244
245
		public boolean hasPrimaryNumber()
246
		{
247
			return _primary_number != null;
248
		}
249
250
		public String getPrimaryNumber()
251
		{
252
			return _primary_number;
253
		}
254
255
		protected void addEmail( String email, int type, boolean is_preferred )
256
		{
257
258
			email = sanitisesEmailAddress( email );
259
			if( email == null )
260
			{
261
				// TODO: warn that an imported email addtrss is being ignored
262
				return;
263
			}
264
265
			// add the email, as non-preferred (we prefer only one email in
266
			// finalise() after they're all imported)
267
			if( _emails == null )
268
				_emails = new HashMap< String, PreferredDetail >();
1 by edam
Initial import
269
			if( !_emails.containsKey( email ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
270
				_emails.put( email, new PreferredDetail( type, false ) );
271
272
			// if this is the first email added, or it's a preferred email
273
			// and a previous email wasn't, then remember that this is the
274
			// "primary email".
275
			if( _primary_email == null ||
276
				( is_preferred && !_primary_email_is_preferred ) )
277
			{
278
				_primary_email = email;
279
				_primary_email_is_preferred = is_preferred;
280
			}
281
		}
282
283
		public boolean hasEmails()
284
		{
285
			return _emails != null && _emails.size() > 0;
286
		}
287
288
		public HashMap< String, PreferredDetail > getEmails()
289
		{
290
			return _emails;
291
		}
292
293
		public boolean hasPrimaryEmail()
294
		{
295
			return _primary_email != null;
296
		}
297
298
		public String getPrimaryEmail()
299
		{
300
			return _primary_email;
1 by edam
Initial import
301
		}
37 by edam
- updated TODO and NEWS
302
303
		protected void addAddress( String address, int type )
304
		{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
305
			address = address.trim();
306
			if( address.length() <= 0 )
307
			{
308
				// TODO: warn that an imported address is being ignored
309
				return;
310
			}
311
37 by edam
- updated TODO and NEWS
312
			if( _addresses == null ) _addresses =
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
313
				new HashMap< String, TypeDetail >();
37 by edam
- updated TODO and NEWS
314
			if( !_addresses.containsKey( address ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
315
				_addresses.put( address, new TypeDetail( type ) );
316
		}
317
318
		public boolean hasAddresses()
319
		{
320
			return _addresses != null && _addresses.size() > 0;
321
		}
322
323
		public HashMap< String, TypeDetail > getAddresses()
324
		{
325
			return _addresses;
326
		}
327
328
		protected void finalise()
329
		{
330
			// ensure that if there is a primary number, it is preferred so
331
			// that there is always one preferred number. Android will assign
332
			// preference to one anyway so we might as well decide one sensibly.
333
			if( _primary_number != null ) {
334
				PreferredDetail data = _numbers.get( _primary_number );
335
				_numbers.put( _primary_number,
336
					new PreferredDetail( data.getType(), true ) );
337
			}
338
339
			// do the same for the primary email
340
			if( _primary_email != null ) {
341
				PreferredDetail data = _emails.get( _primary_email );
342
				_emails.put( _primary_email,
343
					new PreferredDetail( data.getType(), true ) );
344
			}
345
346
			// do the same for the primary organisation
347
			if( _primary_organisation != null ) {
348
				ExtraDetail data = _organisations.get( _primary_organisation );
349
				_organisations.put( _primary_organisation,
350
					new ExtraDetail( 0, true, data.getExtra() ) );
351
			}
352
		}
353
354
		private String sanitisePhoneNumber( String number )
355
		{
356
			number = number.trim();
357
			Pattern p = Pattern.compile( "^[-\\(\\) \\+0-9#*]+" );
358
			Matcher m = p.matcher( number );
359
			if( m.lookingAt() ) return m.group( 0 );
360
			return null;
361
		}
362
363
		private String sanitisesEmailAddress( String email )
364
		{
365
			email = email.trim();
366
			Pattern p = Pattern.compile(
367
				"^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
368
			Matcher m = p.matcher( email );
369
			if( m.matches() ) {
370
				String[] bits = email.split( "@" );
371
				return bits[ 0 ] + "@" + bits[ 1 ].toLowerCase();
372
			}
373
			return null;
37 by edam
- updated TODO and NEWS
374
		}
1 by edam
Initial import
375
	}
376
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
377
	@SuppressWarnings("serial")
1 by edam
Initial import
378
	protected class AbortImportException extends Exception { };
379
380
	public Importer( Doit doit )
381
	{
382
		_doit = doit;
383
384
		SharedPreferences prefs = getSharedPreferences();
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
385
		_merge_setting = prefs.getInt( "merge_setting", Doit.ACTION_PROMPT );
1 by edam
Initial import
386
	}
387
388
	@Override
389
	public void run()
390
	{
391
		try
392
		{
39 by edam
- pulled contacts cache out in to seperate class
393
			// update UI
394
			setProgressMessage( R.string.doit_caching );
395
396
			// build a cache of existing contacts
397
			_contactsCache = new ContactsCache();
398
			_contactsCache.buildCache( _doit );
1 by edam
Initial import
399
400
			// do the import
401
			onImport();
402
403
			// done!
3 by edam
- added "all done" message
404
			finish( ACTION_ALLDONE );
1 by edam
Initial import
405
		}
406
		catch( AbortImportException e )
407
		{}
3 by edam
- added "all done" message
408
409
		// flag as finished to prevent interrupts
410
		setIsFinished();
411
	}
412
413
	synchronized private void setIsFinished()
414
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
415
		_is_finished = true;
1 by edam
Initial import
416
	}
417
418
	protected void onImport() throws AbortImportException
419
	{
420
	}
421
422
	public void wake()
423
	{
424
		wake( 0, RESPONSEEXTRA_NONE );
425
	}
426
427
	public void wake( int response )
428
	{
429
		wake( response, RESPONSEEXTRA_NONE );
430
	}
431
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
432
	synchronized public void wake( int response, int response_extra )
1 by edam
Initial import
433
	{
434
		_response = response;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
435
		_response_extra = response_extra;
1 by edam
Initial import
436
		notify();
437
	}
438
3 by edam
- added "all done" message
439
	synchronized public boolean setAbort()
1 by edam
Initial import
440
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
441
		if( !_is_finished && !_abort ) {
3 by edam
- added "all done" message
442
			_abort = true;
443
			notify();
444
			return true;
445
		}
446
		return false;
1 by edam
Initial import
447
	}
448
449
	protected SharedPreferences getSharedPreferences()
450
	{
451
		return _doit.getSharedPreferences();
452
	}
453
454
	protected void showError( int res ) throws AbortImportException
455
	{
456
		showError( _doit.getText( res ).toString() );
457
	}
458
459
	synchronized protected void showError( String message )
460
			throws AbortImportException
461
	{
462
		checkAbort();
463
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
464
			_doit._handler, Doit.MESSAGE_ERROR, message ) );
1 by edam
Initial import
465
		try {
466
			wait();
467
		}
468
		catch( InterruptedException e ) { }
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
469
3 by edam
- added "all done" message
470
		// no need to check if an abortion happened during the wait, we are
471
		// about to finish anyway!
472
		finish( ACTION_ABORT );
1 by edam
Initial import
473
	}
474
475
	protected void showFatalError( int res ) throws AbortImportException
476
	{
477
		showFatalError( _doit.getText( res ).toString() );
478
	}
479
480
	synchronized protected void showFatalError( String message )
481
			throws AbortImportException
482
	{
483
		checkAbort();
484
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
485
			_doit._handler, Doit.MESSAGE_ERROR, message ) );
1 by edam
Initial import
486
		try {
487
			wait();
488
		}
489
		catch( InterruptedException e ) { }
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
490
3 by edam
- added "all done" message
491
		// no need to check if an abortion happened during the wait, we are
492
		// about to finish anyway!
493
		finish( ACTION_ABORT );
1 by edam
Initial import
494
	}
495
496
	protected boolean showContinue( int res ) throws AbortImportException
497
	{
498
		return showContinue( _doit.getText( res ).toString() );
499
	}
500
501
	synchronized protected boolean showContinue( String message )
502
			throws AbortImportException
503
	{
504
		checkAbort();
505
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
506
			_doit._handler, Doit.MESSAGE_CONTINUEORABORT, message ) );
1 by edam
Initial import
507
		try {
508
			wait();
509
		}
510
		catch( InterruptedException e ) { }
3 by edam
- added "all done" message
511
512
		// check if an abortion happened during the wait
513
		checkAbort();
514
1 by edam
Initial import
515
		return _response == RESPONSE_POSITIVE;
516
	}
517
518
	protected void setProgressMessage( int res ) throws AbortImportException
519
	{
520
		checkAbort();
521
		_doit._handler.sendMessage( Message.obtain( _doit._handler,
36 by edam
- formatting: removed some double-indents on overrunning lines
522
			Doit.MESSAGE_SETPROGRESSMESSAGE, getText( res ) ) );
1 by edam
Initial import
523
	}
524
525
	protected void setProgressMax( int maxProgress )
526
			throws AbortImportException
527
	{
528
		checkAbort();
529
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
530
			_doit._handler, Doit.MESSAGE_SETMAXPROGRESS,
531
			new Integer( maxProgress ) ) );
1 by edam
Initial import
532
	}
533
534
	protected void setTmpProgress( int tmpProgress ) throws AbortImportException
535
	{
536
		checkAbort();
537
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
538
			_doit._handler, Doit.MESSAGE_SETTMPPROGRESS,
539
			new Integer( tmpProgress ) ) );
1 by edam
Initial import
540
	}
541
542
	protected void setProgress( int progress ) throws AbortImportException
543
	{
544
		checkAbort();
545
		_doit._handler.sendMessage( Message.obtain(
36 by edam
- formatting: removed some double-indents on overrunning lines
546
			_doit._handler, Doit.MESSAGE_SETPROGRESS,
547
			new Integer( progress ) ) );
1 by edam
Initial import
548
	}
549
3 by edam
- added "all done" message
550
	protected void finish( int action ) throws AbortImportException
1 by edam
Initial import
551
	{
3 by edam
- added "all done" message
552
		// update UI to reflect action
553
		int message;
554
		switch( action )
555
		{
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
556
		case ACTION_ALLDONE:	message = Doit.MESSAGE_ALLDONE; break;
3 by edam
- added "all done" message
557
		default:	// fall through
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
558
		case ACTION_ABORT:		message = Doit.MESSAGE_ABORT; break;
3 by edam
- added "all done" message
559
		}
560
		_doit._handler.sendEmptyMessage( message );
1 by edam
Initial import
561
3 by edam
- added "all done" message
562
		// stop
563
		throw new AbortImportException();
1 by edam
Initial import
564
	}
565
566
	protected CharSequence getText( int res )
567
	{
568
		return _doit.getText( res );
569
	}
570
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
571
	protected boolean isImportRequired( ContactData contact )
572
			throws AbortImportException, ContactNeedsMoreInfoException
1 by edam
Initial import
573
	{
574
		checkAbort();
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
575
		return isImportRequired( contact, _merge_setting );
1 by edam
Initial import
576
	}
577
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
578
	synchronized private boolean isImportRequired(
579
		ContactData contact, int merge_setting )
580
		throws AbortImportException, ContactNeedsMoreInfoException
1 by edam
Initial import
581
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
582
		_last_merge_decision = merge_setting;
583
584
		// create a cache identifier which we can use to detect if this contact
585
		// is valid for importing
586
		ContactsCache.CacheIdentifier identifier =
587
			ContactsCache.createIdentifier( contact );
588
		if( identifier == null )
589
			throw new ContactNeedsMoreInfoException();
1 by edam
Initial import
590
591
		// handle special cases
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
592
		switch( merge_setting )
1 by edam
Initial import
593
		{
9 by edam
- added scroll view to all layouts
594
		case Doit.ACTION_KEEP:
1 by edam
Initial import
595
			// if we keep contacts on duplicate, we better check for one
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
596
			return !_contactsCache.canLookup( identifier );
1 by edam
Initial import
597
9 by edam
- added scroll view to all layouts
598
		case Doit.ACTION_PROMPT:
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
599
			// if we are prompting on duplicate, we better check for one and if
600
			// the contact doesn'te exist, we want to import it
601
			if( !_contactsCache.canLookup( identifier ) )
1 by edam
Initial import
602
				return true;
603
604
			// ok, it exists, so do prompt
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
605
			_doit._handler.sendMessage( Message.obtain( _doit._handler,
606
				Doit.MESSAGE_MERGEPROMPT, identifier.getDetail() ) );
1 by edam
Initial import
607
			try {
608
				wait();
609
			}
610
			catch( InterruptedException e ) { }
611
3 by edam
- added "all done" message
612
			// check if an abortion happened during the wait
613
			checkAbort();
614
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
615
			// if "always" was selected, make choice permanent
616
			if( _response_extra == RESPONSEEXTRA_ALWAYS )
617
				_merge_setting = _response;
1 by edam
Initial import
618
36 by edam
- formatting: removed some double-indents on overrunning lines
619
			// recurse, with our new merge setting
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
620
			return isImportRequired( contact, _response );
1 by edam
Initial import
621
		}
622
623
		// for all other cases (either overwriting or merging) we will need the
624
		// imported data
625
		return true;
626
	}
627
628
	protected void skipContact() throws AbortImportException
629
	{
630
		checkAbort();
3 by edam
- added "all done" message
631
		_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTSKIPPED );
1 by edam
Initial import
632
	}
633
634
	protected void importContact( ContactData contact )
635
			throws AbortImportException
636
	{
637
		checkAbort();
638
3 by edam
- added "all done" message
639
//		if( !showContinue( "====[ IMPORTING ]====\n: " + contact._name ) )
640
//			finish( ACTION_ABORT );
1 by edam
Initial import
641
642
		ContentValues values = new ContentValues();
643
		boolean uiInformed = false;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
644
		Long id = null;
645
646
		// give the contact a chance to finalise it's data
647
		contact.finalise();
648
649
		// create something, from the contact data, that we can use to identify
650
		// a cache entry and attempt to lookup the id of an existing contact in
651
		// the cache with it
652
		ContactsCache.CacheIdentifier identifier =
653
			ContactsCache.createIdentifier( contact );
654
		if( identifier != null ) id = (Long)_contactsCache.lookup( identifier );
1 by edam
Initial import
655
656
		// does contact exist already?
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
657
		if( id != null )
1 by edam
Initial import
658
		{
659
			// should we skip this import altogether?
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
660
			if( _last_merge_decision == Doit.ACTION_KEEP ) return;
1 by edam
Initial import
661
662
			// get contact's URI
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
663
			Uri contactUri = ContentUris.withAppendedId(
36 by edam
- formatting: removed some double-indents on overrunning lines
664
				Contacts.People.CONTENT_URI, id );
1 by edam
Initial import
665
666
			// should we destroy the existing contact before importing?
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
667
			if( _last_merge_decision == Doit.ACTION_OVERWRITE )
668
			{
669
				// remove from device
1 by edam
Initial import
670
				_doit.getContentResolver().delete( contactUri, null, null );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
671
672
				// update cache
673
				_contactsCache.removeLookup( identifier );
674
				_contactsCache.removeAssociatedData( id );
675
676
				// show that we're overwriting a contact
36 by edam
- formatting: removed some double-indents on overrunning lines
677
				_doit._handler.sendEmptyMessage(
678
						Doit.MESSAGE_CONTACTOVERWRITTEN );
1 by edam
Initial import
679
				uiInformed = true;
680
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
681
				// discard the contact id
682
				id = null;
1 by edam
Initial import
683
			}
684
		}
685
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
686
		// if we don't have a contact id yet (or if we did, but we destroyed it
687
		// when we deleted the contact), we'll have to create a new contact
688
		if( id == null )
1 by edam
Initial import
689
		{
690
			// create a new contact
691
			values.put( Contacts.People.NAME, contact._name );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
692
			Uri contactUri = _doit.getContentResolver().insert(
36 by edam
- formatting: removed some double-indents on overrunning lines
693
				Contacts.People.CONTENT_URI, values );
1 by edam
Initial import
694
			id = ContentUris.parseId( contactUri );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
695
			if( id == null || id <= 0 )
696
				showError( R.string.error_unabletoaddcontact );
1 by edam
Initial import
697
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
698
			// try to add them to the "My Contacts" group
699
			try {
700
				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.
701
					_doit.getContentResolver(), id );
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
702
			}
36 by edam
- formatting: removed some double-indents on overrunning lines
703
			catch( IllegalStateException e ) {
704
				// ignore any failure
705
			}
1 by edam
Initial import
706
707
			// update cache
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
708
			_contactsCache.addLookup(
709
				ContactsCache.createIdentifier( contact ), id );
1 by edam
Initial import
710
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
711
			// if we haven't already shown that we're overwriting a contact,
712
			// show that we're creating a new contact
1 by edam
Initial import
713
			if( !uiInformed ) {
3 by edam
- added "all done" message
714
				_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTCREATED );
1 by edam
Initial import
715
				uiInformed = true;
716
			}
717
		}
718
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
719
		// if we haven't already shown that we're overwriting or creating a
720
		// contact show that we're merging a contact
1 by edam
Initial import
721
		if( !uiInformed )
3 by edam
- added "all done" message
722
			_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTMERGED );
1 by edam
Initial import
723
724
		// import contact parts
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
725
		if( contact.hasNumbers() )
726
			importContactPhones( id, contact.getNumbers() );
727
		if( contact.hasEmails() )
728
			importContactEmails( id, contact.getEmails() );
729
		if( contact.hasAddresses() )
730
			importContactAddresses( id, contact.getAddresses() );
731
		if( contact.hasOrganisations() )
732
			importContactOrganisations( id, contact.getOrganisations() );
1 by edam
Initial import
733
	}
734
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
735
	private void importContactPhones( Long id,
736
			HashMap< String, ContactData.PreferredDetail > datas )
1 by edam
Initial import
737
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
738
		// get URI to contact's phones
739
		Uri contactPhonesUri = Uri.withAppendedPath(
740
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
741
			Contacts.People.Phones.CONTENT_DIRECTORY );
742
		Set< String > datasKeys = datas.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!?)
743
744
		// add phone numbers
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
745
		Iterator< String > i = datasKeys.iterator();
1 by edam
Initial import
746
		while( i.hasNext() ) {
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
747
			String number = i.next();
748
			ContactData.PreferredDetail data = datas.get( number );
1 by edam
Initial import
749
750
			// we don't want to add this number if it's crap, or it already
751
			// exists (which would cause a duplicate to be created). We don't
752
			// take in to account the type when checking for duplicates. This is
753
			// intentional: types aren't really very reliable. We assume that
754
			// if the number exists at all, it doesn't need importing. Because
755
			// of this, we also can't update the cache (which we don't need to
756
			// anyway, so it's not a problem).
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
757
			if( _contactsCache.hasAssociatedNumber( id, number ) )
758
				continue;
1 by edam
Initial import
759
760
			// add phone number
761
			ContentValues values = new ContentValues();
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
762
			values.put( Contacts.Phones.TYPE, data.getType() );
763
			values.put( Contacts.Phones.NUMBER, number );
764
			if( data.isPreferred() )
765
				values.put( Contacts.Phones.ISPRIMARY, 1 );
1 by edam
Initial import
766
			_doit.getContentResolver().insert( contactPhonesUri, values );
37 by edam
- updated TODO and NEWS
767
768
			// and add this address to the cache to prevent a addition of
769
			// duplicate date from another file
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
770
			_contactsCache.addAssociatedNumber( id, 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!?)
771
		}
1 by edam
Initial import
772
	}
773
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
774
	private void importContactEmails( Long id,
775
			HashMap< String, ContactData.PreferredDetail > datas )
1 by edam
Initial import
776
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
777
		// get URI to contact's contact methods
778
		Uri contactContactMethodsUri = Uri.withAppendedPath(
779
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
780
			Contacts.People.ContactMethods.CONTENT_DIRECTORY );
781
		Set< String > datasKeys = datas.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!?)
782
783
		// add email addresses
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
784
		Iterator< String > i = datasKeys.iterator();
1 by edam
Initial import
785
		while( i.hasNext() ) {
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
786
			String email = i.next();
787
			ContactData.PreferredDetail data = datas.get( email );
1 by edam
Initial import
788
37 by edam
- updated TODO and NEWS
789
			// we don't want to add this email address if it exists already or
790
			// we would introduce duplicates.
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
791
			if( _contactsCache.hasAssociatedEmail( id, email ) )
792
				continue;
1 by edam
Initial import
793
794
			// add phone number
795
			ContentValues values = new ContentValues();
796
			values.put( Contacts.ContactMethods.KIND, Contacts.KIND_EMAIL );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
797
			values.put( Contacts.ContactMethods.DATA, email );
798
			values.put( Contacts.ContactMethods.TYPE, data.getType() );
799
			if( data.isPreferred() )
1 by edam
Initial import
800
				values.put( Contacts.ContactMethods.ISPRIMARY, 1 );
801
			_doit.getContentResolver().insert( contactContactMethodsUri,
36 by edam
- formatting: removed some double-indents on overrunning lines
802
				values );
37 by edam
- updated TODO and NEWS
803
804
			// and add this address to the cache to prevent a addition of
805
			// duplicate date from another file
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
806
			_contactsCache.addAssociatedEmail( id, email );
1 by edam
Initial import
807
		}
37 by edam
- updated TODO and NEWS
808
	}
809
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
810
	private void importContactAddresses( Long id,
811
		HashMap< String, ContactData.TypeDetail > datas )
37 by edam
- updated TODO and NEWS
812
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
813
		// get URI to contact's contact methods
814
		Uri contactContactMethodsUri = Uri.withAppendedPath(
815
			ContentUris.withAppendedId( Contacts.People.CONTENT_URI, id ),
816
			Contacts.People.ContactMethods.CONTENT_DIRECTORY );
37 by edam
- updated TODO and NEWS
817
818
		// add addresses
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
819
		Set< String > datasKeys = datas.keySet();
820
		Iterator< String > i = datasKeys.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!?)
821
		while( i.hasNext() ) {
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
822
			String address = i.next();
823
			ContactData.TypeDetail data = datas.get( address );
37 by edam
- updated TODO and NEWS
824
825
			// we don't want to add this address if it exists already or we
826
			// would introduce duplicates
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
827
			if( _contactsCache.hasAssociatedAddress( id, address ) )
37 by edam
- updated TODO and NEWS
828
				continue;
829
830
			// add postal address
831
			ContentValues values = new ContentValues();
832
			values.put( Contacts.ContactMethods.KIND, Contacts.KIND_POSTAL );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
833
			values.put( Contacts.ContactMethods.DATA, address );
834
			values.put( Contacts.ContactMethods.TYPE, data.getType() );
37 by edam
- updated TODO and NEWS
835
			_doit.getContentResolver().insert( contactContactMethodsUri,
836
				values );
837
838
			// and add this address to the cache to prevent a addition of
839
			// duplicate date from another file
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
840
			_contactsCache.addAssociatedAddress( id, address );
841
		}
842
	}
843
844
	private void importContactOrganisations( Long id,
845
		HashMap< String, ContactData.ExtraDetail > datas )
846
	{
847
		// add addresses
848
		Set< String > datasKeys = datas.keySet();
849
		Iterator< String > i = datasKeys.iterator();
850
		while( i.hasNext() ) {
851
			String organisation = i.next();
852
			ContactData.ExtraDetail data = datas.get( organisation );
853
854
			// we don't want to add this address if it exists already or we
855
			// would introduce duplicates
856
			if( _contactsCache.hasAssociatedOrganisation( id, organisation ) )
857
				continue;
858
859
			// add organisation address
860
			ContentValues values = new ContentValues();
861
			values.put( Contacts.Organizations.PERSON_ID, id );
862
			values.put( Contacts.Organizations.COMPANY, organisation );
863
			values.put( Contacts.ContactMethods.TYPE,
864
				Contacts.OrganizationColumns.TYPE_WORK );
865
			if( data.getExtra() != null )
866
				values.put( Contacts.Organizations.TITLE, data.getExtra() );
867
			_doit.getContentResolver().insert(
868
				Contacts.Organizations.CONTENT_URI, values );
869
870
			// and add this address to the cache to prevent a addition of
871
			// duplicate date from another file
872
			_contactsCache.addAssociatedOrganisation( id, organisation );
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!?)
873
		}
1 by edam
Initial import
874
	}
875
3 by edam
- added "all done" message
876
	synchronized protected void checkAbort() throws AbortImportException
1 by edam
Initial import
877
	{
878
		if( _abort ) {
879
			// stop
880
			throw new AbortImportException();
881
		}
882
	}
883
}