/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts

« back to all changes in this revision

Viewing changes to src/org/waxworlds/importcontacts/Importer.java

  • Committer: edam
  • Date: 2009-01-13 06:35:26 UTC
  • Revision ID: edam@waxworlds.org-20090113063526-l9t1s9git4bav60a
- 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!?)

Show diffs side-by-side

added added

removed removed

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