/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
 *
57 by edam
cleanup; fixed some typos; updated TODO
4
 * Copyright (C) 2009 to 2012 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
38
public class Importer extends Thread
39
{
3 by edam
- added "all done" message
40
	public final static int ACTION_ABORT = 1;
41
	public final static int ACTION_ALLDONE = 2;
1 by edam
Initial import
42
43
	public final static int RESPONSE_NEGATIVE = 0;
44
	public final static int RESPONSE_POSITIVE = 1;
45
46
	public final static int RESPONSEEXTRA_NONE = 0;
47
	public final static int RESPONSEEXTRA_ALWAYS = 1;
48
49
	private Doit _doit;
50
	private int _response;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
51
	private int _response_extra;
52
	private int _merge_setting;
53
	private int _last_merge_decision;
1 by edam
Initial import
54
	private boolean _abort = false;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
55
	private boolean _is_finished = false;
41 by edam
- updated TODO
56
	private ContactsCache _contacts_cache = null;
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
57
	private Backend _backend = null;
1 by edam
Initial import
58
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
59
	/**
60
	 * Data about a contact
61
	 */
1 by edam
Initial import
62
	public class ContactData
63
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
64
		class TypeDetail
65
		{
66
			protected int _type;
67
68
			public TypeDetail( int type )
69
			{
70
				_type = type;
71
			}
72
73
			public int getType()
74
			{
75
				return _type;
76
			}
77
		}
78
79
		class PreferredDetail extends TypeDetail
80
		{
81
			protected boolean _is_preferred;
82
83
			public PreferredDetail( int type, boolean is_preferred )
84
			{
85
				super( type );
86
				_is_preferred = is_preferred;
87
			}
88
89
			public boolean isPreferred()
90
			{
91
				return _is_preferred;
92
			}
93
		}
94
95
		class ExtraDetail extends PreferredDetail
96
		{
97
			protected String _extra;
98
99
			public ExtraDetail( int type, boolean is_preferred, String extra )
100
			{
101
				super( type, is_preferred );
102
103
				if( extra != null ) extra = extra.trim();
104
				_extra = extra;
105
			}
106
107
			public String getExtra()
108
			{
109
				return _extra;
110
			}
111
112
			public void setExtra( String extra )
113
			{
114
				if( extra != null ) extra = extra.trim();
115
				_extra = extra;
116
			}
117
		}
118
43 by edam
- refactored some code to do with how contacts are imported
119
		@SuppressWarnings("serial")
120
		protected class ContactNotIdentifiableException extends Exception
121
		{
122
		}
123
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
124
		protected String _name = null;
125
		protected String _primary_organisation = null;
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
126
		protected boolean _primary_organisation_is_preferred;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
127
		protected String _primary_number = null;
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
128
		protected int _primary_number_type;
129
		protected boolean _primary_number_is_preferred;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
130
		protected String _primary_email = null;
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
131
		protected boolean _primary_email_is_preferred;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
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
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
178
			// organisation and the current primary organisation isn't, then
179
			// record this as the primary organisation.
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
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
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
226
			final Set< Integer > non_voice_types = new HashSet< Integer >(
227
				Arrays.asList( PhonesColumns.TYPE_FAX_HOME,
228
					PhonesColumns.TYPE_FAX_WORK, PhonesColumns.TYPE_PAGER ) );
229
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
230
			// 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
231
			// and the current primary number isn't, or this number is on equal
232
			// standing with the primary number in terms of preference and it is
233
			// a voice number and the primary number isn't, then record this as
234
			// the primary number.
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
235
			if( _primary_number == null ||
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
236
				( is_preferred && !_primary_number_is_preferred ) ||
237
				( is_preferred == _primary_number_is_preferred &&
238
					!non_voice_types.contains( type ) &&
239
					non_voice_types.contains( _primary_number_type ) ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
240
			{
241
				_primary_number = number;
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
242
				_primary_number_type = type;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
243
				_primary_number_is_preferred = is_preferred;
244
			}
245
		}
246
247
		public boolean hasNumbers()
248
		{
249
			return _numbers != null && _numbers.size() > 0;
250
		}
251
252
		public HashMap< String, PreferredDetail > getNumbers()
253
		{
254
			return _numbers;
255
		}
256
257
		public boolean hasPrimaryNumber()
258
		{
259
			return _primary_number != null;
260
		}
261
262
		public String getPrimaryNumber()
263
		{
264
			return _primary_number;
265
		}
266
267
		protected void addEmail( String email, int type, boolean is_preferred )
268
		{
269
270
			email = sanitisesEmailAddress( email );
271
			if( email == null )
272
			{
57 by edam
cleanup; fixed some typos; updated TODO
273
				// TODO: warn that an imported email address is being ignored
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
274
				return;
275
			}
276
277
			// add the email, as non-preferred (we prefer only one email in
278
			// finalise() after they're all imported)
279
			if( _emails == null )
280
				_emails = new HashMap< String, PreferredDetail >();
1 by edam
Initial import
281
			if( !_emails.containsKey( email ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
282
				_emails.put( email, new PreferredDetail( type, false ) );
283
46 by edam
- properly handle multiple TYPE= params in one entry in a v3.0 vCard
284
			// if this is the first email added, or it's a preferred email and
285
			// the current primary organisation isn't, then record this as the
286
			// primary email.
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
287
			if( _primary_email == null ||
288
				( is_preferred && !_primary_email_is_preferred ) )
289
			{
290
				_primary_email = email;
291
				_primary_email_is_preferred = is_preferred;
292
			}
293
		}
294
295
		public boolean hasEmails()
296
		{
297
			return _emails != null && _emails.size() > 0;
298
		}
299
300
		public HashMap< String, PreferredDetail > getEmails()
301
		{
302
			return _emails;
303
		}
304
305
		public boolean hasPrimaryEmail()
306
		{
307
			return _primary_email != null;
308
		}
309
310
		public String getPrimaryEmail()
311
		{
312
			return _primary_email;
1 by edam
Initial import
313
		}
37 by edam
- updated TODO and NEWS
314
315
		protected void addAddress( String address, int type )
316
		{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
317
			address = address.trim();
318
			if( address.length() <= 0 )
319
			{
320
				// TODO: warn that an imported address is being ignored
321
				return;
322
			}
323
37 by edam
- updated TODO and NEWS
324
			if( _addresses == null ) _addresses =
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
325
				new HashMap< String, TypeDetail >();
37 by edam
- updated TODO and NEWS
326
			if( !_addresses.containsKey( address ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
327
				_addresses.put( address, new TypeDetail( type ) );
328
		}
329
330
		public boolean hasAddresses()
331
		{
332
			return _addresses != null && _addresses.size() > 0;
333
		}
334
335
		public HashMap< String, TypeDetail > getAddresses()
336
		{
337
			return _addresses;
338
		}
339
340
		protected void finalise()
43 by edam
- refactored some code to do with how contacts are imported
341
			throws ContactNotIdentifiableException
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
342
		{
343
			// ensure that if there is a primary number, it is preferred so
344
			// that there is always one preferred number. Android will assign
345
			// preference to one anyway so we might as well decide one sensibly.
346
			if( _primary_number != null ) {
347
				PreferredDetail data = _numbers.get( _primary_number );
348
				_numbers.put( _primary_number,
349
					new PreferredDetail( data.getType(), true ) );
350
			}
351
352
			// do the same for the primary email
353
			if( _primary_email != null ) {
354
				PreferredDetail data = _emails.get( _primary_email );
355
				_emails.put( _primary_email,
356
					new PreferredDetail( data.getType(), true ) );
357
			}
358
359
			// do the same for the primary organisation
360
			if( _primary_organisation != null ) {
361
				ExtraDetail data = _organisations.get( _primary_organisation );
362
				_organisations.put( _primary_organisation,
363
					new ExtraDetail( 0, true, data.getExtra() ) );
364
			}
43 by edam
- refactored some code to do with how contacts are imported
365
366
			// create a cache identifier from this contact data, which can be
367
			// used to look-up an existing contact
368
			_cache_identifier = ContactsCache.createIdentifier( this );
369
			if( _cache_identifier == null )
370
				throw new ContactNotIdentifiableException();
371
		}
372
373
		public ContactsCache.CacheIdentifier getCacheIdentifier()
374
		{
375
			return _cache_identifier;
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
376
		}
377
378
		private String sanitisePhoneNumber( String number )
379
		{
380
			number = number.trim();
381
			Pattern p = Pattern.compile( "^[-\\(\\) \\+0-9#*]+" );
382
			Matcher m = p.matcher( number );
383
			if( m.lookingAt() ) return m.group( 0 );
384
			return null;
385
		}
386
387
		private String sanitisesEmailAddress( String email )
388
		{
389
			email = email.trim();
390
			Pattern p = Pattern.compile(
391
				"^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
392
			Matcher m = p.matcher( email );
393
			if( m.matches() ) {
394
				String[] bits = email.split( "@" );
395
				return bits[ 0 ] + "@" + bits[ 1 ].toLowerCase();
396
			}
397
			return null;
37 by edam
- updated TODO and NEWS
398
		}
1 by edam
Initial import
399
	}
400
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
401
	@SuppressWarnings("serial")
1 by edam
Initial import
402
	protected class AbortImportException extends Exception { };
403
404
	public Importer( Doit doit )
405
	{
406
		_doit = doit;
407
408
		SharedPreferences prefs = getSharedPreferences();
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
409
		_merge_setting = prefs.getInt( "merge_setting", Doit.ACTION_PROMPT );
1 by edam
Initial import
410
	}
411
412
	@Override
413
	public void run()
414
	{
415
		try
416
		{
39 by edam
- pulled contacts cache out in to seperate class
417
			// update UI
418
			setProgressMessage( R.string.doit_caching );
419
57 by edam
cleanup; fixed some typos; updated TODO
420
			// create the appropriate backend
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 )
57 by edam
cleanup; fixed some typos; updated TODO
422
//				_backend = new ContactsContractBackend( _doit );
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
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 );
57 by edam
cleanup; fixed some typos; updated TODO
714
			if( id == null )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
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
}