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