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