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