/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

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