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