/android/import-contacts

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