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