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