/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;
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
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
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
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
		{
368
			// ensure that if there is a primary number, it is preferred so
369
			// that there is always one preferred number. Android will assign
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.
630
	 * @param contact_detail the display name of the contact
631
	 * @param exists true if this contact matches one in the cache
632
	 * @param merge_setting the merge setting to use
633
	 * @return true if the contact should be skipped outright
634
	 * @throws AbortImportException
635
	 */
636
	synchronized private boolean shouldWeSkipContact( String contact_detail,
637
		boolean exists, int merge_setting ) throws AbortImportException
1 by edam
Initial import
638
	{
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
639
		_last_merge_decision = merge_setting;
640
1 by edam
Initial import
641
		// handle special cases
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
642
		switch( merge_setting )
1 by edam
Initial import
643
		{
9 by edam
- added scroll view to all layouts
644
		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
645
			// if we are skipping on a duplicate, check for one
646
			return exists;
1 by edam
Initial import
647
9 by edam
- added scroll view to all layouts
648
		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
649
			// if we are prompting on duplicate, then we can say that we won't
650
			// skip if there isn't one
651
			if( !exists ) return false;
1 by edam
Initial import
652
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
653
			// ok, duplicate exists, so do prompt
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
654
			_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
655
				Doit.MESSAGE_MERGEPROMPT, contact_detail ) );
1 by edam
Initial import
656
			try {
657
				wait();
658
			}
659
			catch( InterruptedException e ) { }
660
3 by edam
- added "all done" message
661
			// check if an abortion happened during the wait
662
			checkAbort();
663
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
664
			// if "always" was selected, make choice permanent
665
			if( _response_extra == RESPONSEEXTRA_ALWAYS )
666
				_merge_setting = _response;
1 by edam
Initial import
667
36 by edam
- formatting: removed some double-indents on overrunning lines
668
			// 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
669
			return shouldWeSkipContact( contact_detail, exists, _response );
1 by edam
Initial import
670
		}
671
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
672
		// for all other cases (either overwriting or merging) we don't skip
673
		return false;
1 by edam
Initial import
674
	}
675
676
	protected void skipContact() throws AbortImportException
677
	{
678
		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
679
680
		// show that we're skipping a new contact
3 by edam
- added "all done" message
681
		_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTSKIPPED );
1 by edam
Initial import
682
	}
683
684
	protected void importContact( ContactData contact )
685
			throws AbortImportException
686
	{
687
		checkAbort();
688
43 by edam
- refactored some code to do with how contacts are imported
689
		// It is expected that we use contact.getCacheIdentifier() here. The
690
		// contact we are passed should have been successfully finalise()d,
691
		// which includes generating a valid cache identifier.
692
		ContactsCache.CacheIdentifier cache_identifier =
693
			contact.getCacheIdentifier();
694
3 by edam
- added "all done" message
695
//		if( !showContinue( "====[ IMPORTING ]====\n: " + contact._name ) )
696
//			finish( ACTION_ABORT );
1 by edam
Initial import
697
43 by edam
- refactored some code to do with how contacts are imported
698
		// attempt to lookup the id of an existing contact in the cache with
699
		// this contact data's cache identifier
700
		Long id = (Long)_contacts_cache.lookup( cache_identifier );
1 by edam
Initial import
701
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
702
		// check to see if this contact should be skipped
703
		if( shouldWeSkipContact( cache_identifier.getDetail(), id != null,
704
			_merge_setting ) )
705
		{
706
			// show that we're skipping a contact
707
			_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTSKIPPED );
708
			return;
709
		}
710
711
		// if a contact exists, and we're overwriting, destroy the existing
712
		// contact before importing
713
		boolean contact_deleted = false;
714
		if( id != null && _last_merge_decision == Doit.ACTION_OVERWRITE )
715
		{
716
			contact_deleted = true;
717
718
			// remove from device
719
			_backend.deleteContact( id );
720
721
			// update cache
722
			_contacts_cache.removeLookup( cache_identifier );
723
			_contacts_cache.removeAssociatedData( id );
724
725
			// show that we're overwriting a contact
726
			_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTOVERWRITTEN );
727
728
			// discard the contact id
729
			id = null;
730
		}
731
732
		try {
733
			// if we don't have a contact id yet (or we did, but we destroyed it
734
			// when we deleted the contact), we'll have to create a new contact
735
			if( id == null )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
736
			{
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
737
				// create a new contact
738
				id = _backend.addContact( contact._name );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
739
740
				// 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
741
				_contacts_cache.addLookup( cache_identifier, id );
742
743
				// if we haven't already shown that we're overwriting a contact,
744
				// show that we're creating a new contact
745
				if( !contact_deleted )
746
					_doit._handler.sendEmptyMessage(
747
						Doit.MESSAGE_CONTACTCREATED );
1 by edam
Initial import
748
			}
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
749
			else
750
				// show that we're merging with an existing contact
751
				_doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTMERGED );
752
753
			// import contact parts
754
			if( contact.hasNumbers() )
755
				importContactPhones( id, contact.getNumbers() );
756
			if( contact.hasEmails() )
757
				importContactEmails( id, contact.getEmails() );
758
			if( contact.hasAddresses() )
759
				importContactAddresses( id, contact.getAddresses() );
760
			if( contact.hasOrganisations() )
761
				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
762
			if( contact.hasNotes() )
763
				importContactNotes( id, contact.getNotes() );
1 by edam
Initial import
764
		}
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
765
		catch( Backend.ContactCreationException e )
1 by edam
Initial import
766
		{
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
767
			showError( R.string.error_unabletoaddcontact );
1 by edam
Initial import
768
		}
769
	}
770
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
771
	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
772
		HashMap< String, ContactData.PreferredDetail > datas )
773
		throws ContactCreationException
1 by edam
Initial import
774
	{
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!?)
775
		// add phone numbers
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
776
		Set< String > datas_keys = datas.keySet();
41 by edam
- updated TODO
777
		Iterator< String > i = datas_keys.iterator();
1 by edam
Initial import
778
		while( i.hasNext() ) {
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
779
			String number = i.next();
780
			ContactData.PreferredDetail data = datas.get( number );
1 by edam
Initial import
781
782
			// we don't want to add this number if it's crap, or it already
783
			// exists (which would cause a duplicate to be created). We don't
784
			// take in to account the type when checking for duplicates. This is
785
			// intentional: types aren't really very reliable. We assume that
786
			// if the number exists at all, it doesn't need importing. Because
787
			// of this, we also can't update the cache (which we don't need to
788
			// anyway, so it's not a problem).
41 by edam
- updated TODO
789
			if( _contacts_cache.hasAssociatedNumber( id, number ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
790
				continue;
1 by edam
Initial import
791
792
			// add phone number
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
793
			_backend.addContactPhone( id, number, data );
37 by edam
- updated TODO and NEWS
794
795
			// and add this address to the cache to prevent a addition of
796
			// duplicate date from another file
41 by edam
- updated TODO
797
			_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!?)
798
		}
1 by edam
Initial import
799
	}
800
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
801
	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
802
		HashMap< String, ContactData.PreferredDetail > datas )
803
		throws ContactCreationException
1 by edam
Initial import
804
	{
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!?)
805
		// add email addresses
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
806
		Set< String > datas_keys = datas.keySet();
41 by edam
- updated TODO
807
		Iterator< String > i = datas_keys.iterator();
1 by edam
Initial import
808
		while( i.hasNext() ) {
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
809
			String email = i.next();
810
			ContactData.PreferredDetail data = datas.get( email );
1 by edam
Initial import
811
37 by edam
- updated TODO and NEWS
812
			// we don't want to add this email address if it exists already or
813
			// we would introduce duplicates.
41 by edam
- updated TODO
814
			if( _contacts_cache.hasAssociatedEmail( id, email ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
815
				continue;
1 by edam
Initial import
816
817
			// add phone number
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
818
			_backend.addContactEmail( id, email, data );
37 by edam
- updated TODO and NEWS
819
820
			// and add this address to the cache to prevent a addition of
821
			// duplicate date from another file
41 by edam
- updated TODO
822
			_contacts_cache.addAssociatedEmail( id, email );
1 by edam
Initial import
823
		}
37 by edam
- updated TODO and NEWS
824
	}
825
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
826
	private void importContactAddresses( Long id,
827
		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
828
		throws ContactCreationException
37 by edam
- updated TODO and NEWS
829
	{
830
		// add addresses
41 by edam
- updated TODO
831
		Set< String > datas_keys = datas.keySet();
832
		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!?)
833
		while( i.hasNext() ) {
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
834
			String address = i.next();
835
			ContactData.TypeDetail data = datas.get( address );
37 by edam
- updated TODO and NEWS
836
837
			// we don't want to add this address if it exists already or we
838
			// would introduce duplicates
41 by edam
- updated TODO
839
			if( _contacts_cache.hasAssociatedAddress( id, address ) )
37 by edam
- updated TODO and NEWS
840
				continue;
841
842
			// add postal address
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
843
			_backend.addContactAddresses( id, address, data );
37 by edam
- updated TODO and NEWS
844
845
			// and add this address to the cache to prevent a addition of
846
			// duplicate date from another file
41 by edam
- updated TODO
847
			_contacts_cache.addAssociatedAddress( id, address );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
848
		}
849
	}
850
851
	private void importContactOrganisations( Long id,
852
		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
853
		throws ContactCreationException
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
854
	{
855
		// add addresses
41 by edam
- updated TODO
856
		Set< String > datas_keys = datas.keySet();
857
		Iterator< String > i = datas_keys.iterator();
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
858
		while( i.hasNext() ) {
859
			String organisation = i.next();
860
			ContactData.ExtraDetail data = datas.get( organisation );
861
862
			// we don't want to add this address if it exists already or we
863
			// would introduce duplicates
41 by edam
- updated TODO
864
			if( _contacts_cache.hasAssociatedOrganisation( id, organisation ) )
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
865
				continue;
866
867
			// add organisation address
53 by edam
abstracted the android contacts API in to an interface, ready to be switched to
868
			_backend.addContactOrganisation( id, organisation, data );
40 by edam
- fixed logic for vcard field types (home, work, cell, etc) so it works
869
870
			// and add this address to the cache to prevent a addition of
871
			// duplicate date from another file
41 by edam
- updated TODO
872
			_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!?)
873
		}
1 by edam
Initial import
874
	}
875
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
876
	private void importContactNotes( Long id,
877
		HashSet< String > datas )
878
		throws ContactCreationException
879
	{
880
		// add notes
881
		Iterator< String > i = datas.iterator();
882
		while( i.hasNext() ) {
883
			String note = i.next();
884
885
			// we don't want to add this note if it exists already or we would
886
			// introduce duplicates
887
			if( _contacts_cache.hasAssociatedNote( id, note ) )
888
				continue;
889
890
			// add note
891
			_backend.addContactNote( id, note );
892
893
			// and add this note to the cache to prevent a addition of duplicate
894
			// date from another file
895
			_contacts_cache.addAssociatedNote( id, note );
896
		}
897
898
	}
899
3 by edam
- added "all done" message
900
	synchronized protected void checkAbort() throws AbortImportException
1 by edam
Initial import
901
	{
902
		if( _abort ) {
903
			// stop
904
			throw new AbortImportException();
905
		}
906
	}
907
}