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