/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/edam/importcontacts/Importer.java

  • Committer: edam
  • Date: 2011-03-19 20:33:09 UTC
  • Revision ID: edam@waxworlds.org-20110319203309-5dzfyqrxwk94jtin
- formatting: removed some double-indents on overrunning lines
- updated TODO and NEWS
- rewrote central logic of parser so it makes more sense, looks nicer and has a small optimisation (getting name and params from line only when necessary)
- optimised unnecessary mutliple converting of lines to US-ASCII
- re-wrote line extraction from vcards so that we can lookahead for v3 folded lines
- added support for v3 folded lines

Show diffs side-by-side

added added

removed removed

1
 
package org.waxworlds.importcontacts;
 
1
/*
 
2
 * Importer.java
 
3
 *
 
4
 * Copyright (C) 2009 Tim Marston <edam@waxworlds.org>
 
5
 *
 
6
 * This file is part of the Import Contacts program (hereafter referred
 
7
 * to as "this program"). For more information, see
 
8
 * http://www.waxworlds.org/edam/software/android/import-contacts
 
9
 *
 
10
 * This program is free software: you can redistribute it and/or modify
 
11
 * it under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 *
 
15
 * This program is distributed in the hope that it will be useful,
 
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
 * GNU General Public License for more details.
 
19
 *
 
20
 * You should have received a copy of the GNU General Public License
 
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
22
 */
 
23
 
 
24
package org.waxworlds.edam.importcontacts;
2
25
 
3
26
import java.util.HashMap;
4
27
import java.util.HashSet;
17
40
 
18
41
public class Importer extends Thread
19
42
{
20
 
        public final static int ACTION_GOBACK = 0;
21
43
        public final static int ACTION_ABORT = 1;
22
44
        public final static int ACTION_ALLDONE = 2;
23
45
 
109
131
                        if( _phones == null ) _phones = new HashMap< String, PhoneData >();
110
132
                        if( !_phones.containsKey( number ) )
111
133
                                _phones.put( number,
112
 
                                                new PhoneData( number, type, isPreferred ) );
 
134
                                        new PhoneData( number, type, isPreferred ) );
113
135
                }
114
136
 
115
137
                protected void addEmail( String email, int type, boolean isPreferred )
120
142
                }
121
143
        }
122
144
 
 
145
        @SuppressWarnings("serial")
123
146
        protected class AbortImportException extends Exception { };
124
147
 
125
148
        public Importer( Doit doit )
127
150
                _doit = doit;
128
151
 
129
152
                SharedPreferences prefs = getSharedPreferences();
130
 
                _mergeSetting = prefs.getInt( "merge_setting", 0 );
 
153
                _mergeSetting = prefs.getInt( "merge_setting", Doit.ACTION_PROMPT );
131
154
        }
132
155
 
133
156
        @Override
202
225
        {
203
226
                checkAbort();
204
227
                _doit._handler.sendMessage( Message.obtain(
205
 
                                _doit._handler, Doit.MESSAGE_ERROR, message ) );
 
228
                        _doit._handler, Doit.MESSAGE_ERROR, message ) );
206
229
                try {
207
230
                        wait();
208
231
                }
209
232
                catch( InterruptedException e ) { }
 
233
 
210
234
                // no need to check if an abortion happened during the wait, we are
211
235
                // about to finish anyway!
212
236
                finish( ACTION_ABORT );
222
246
        {
223
247
                checkAbort();
224
248
                _doit._handler.sendMessage( Message.obtain(
225
 
                                _doit._handler, Doit.MESSAGE_ERROR, message ) );
 
249
                        _doit._handler, Doit.MESSAGE_ERROR, message ) );
226
250
                try {
227
251
                        wait();
228
252
                }
229
253
                catch( InterruptedException e ) { }
 
254
 
230
255
                // no need to check if an abortion happened during the wait, we are
231
256
                // about to finish anyway!
232
257
                finish( ACTION_ABORT );
242
267
        {
243
268
                checkAbort();
244
269
                _doit._handler.sendMessage( Message.obtain(
245
 
                                _doit._handler, Doit.MESSAGE_CONTINUEORABORT, message ) );
 
270
                        _doit._handler, Doit.MESSAGE_CONTINUEORABORT, message ) );
246
271
                try {
247
272
                        wait();
248
273
                }
258
283
        {
259
284
                checkAbort();
260
285
                _doit._handler.sendMessage( Message.obtain( _doit._handler,
261
 
                                Doit.MESSAGE_SETPROGRESSMESSAGE, getText( res ) ) );
 
286
                        Doit.MESSAGE_SETPROGRESSMESSAGE, getText( res ) ) );
262
287
        }
263
288
 
264
289
        protected void setProgressMax( int maxProgress )
266
291
        {
267
292
                checkAbort();
268
293
                _doit._handler.sendMessage( Message.obtain(
269
 
                                _doit._handler, Doit.MESSAGE_SETMAXPROGRESS,
270
 
                                new Integer( maxProgress ) ) );
 
294
                        _doit._handler, Doit.MESSAGE_SETMAXPROGRESS,
 
295
                        new Integer( maxProgress ) ) );
271
296
        }
272
297
 
273
298
        protected void setTmpProgress( int tmpProgress ) throws AbortImportException
274
299
        {
275
300
                checkAbort();
276
301
                _doit._handler.sendMessage( Message.obtain(
277
 
                                _doit._handler, Doit.MESSAGE_SETTMPPROGRESS,
278
 
                                new Integer( tmpProgress ) ) );
 
302
                        _doit._handler, Doit.MESSAGE_SETTMPPROGRESS,
 
303
                        new Integer( tmpProgress ) ) );
279
304
        }
280
305
 
281
306
        protected void setProgress( int progress ) throws AbortImportException
282
307
        {
283
308
                checkAbort();
284
309
                _doit._handler.sendMessage( Message.obtain(
285
 
                                _doit._handler, Doit.MESSAGE_SETPROGRESS,
286
 
                                new Integer( progress ) ) );
 
310
                        _doit._handler, Doit.MESSAGE_SETPROGRESS,
 
311
                        new Integer( progress ) ) );
287
312
        }
288
313
 
289
314
        protected void finish( int action ) throws AbortImportException
292
317
                int message;
293
318
                switch( action )
294
319
                {
295
 
                case ACTION_GOBACK:             message = Doit.MESSAGE_FINISHED_GOBACK; break;
296
 
                case ACTION_ALLDONE:    message = Doit.MESSAGE_FINISHED_ALLDONE; break;
 
320
                case ACTION_ALLDONE:    message = Doit.MESSAGE_ALLDONE; break;
297
321
                default:        // fall through
298
 
                case ACTION_ABORT:              message = Doit.MESSAGE_FINISHED; break;
 
322
                case ACTION_ABORT:              message = Doit.MESSAGE_ABORT; break;
299
323
                }
300
324
                _doit._handler.sendEmptyMessage( message );
301
325
 
323
347
                // handle special cases
324
348
                switch( mergeSetting )
325
349
                {
326
 
                case R.id.merge_keep:
 
350
                case Doit.ACTION_KEEP:
327
351
                        // if we keep contacts on duplicate, we better check for one
328
352
                        return !_contacts.containsKey( name );
329
353
 
330
 
                case R.id.merge_prompt:
 
354
                case Doit.ACTION_PROMPT:
331
355
                        // if we are prompting on duplicate, we better check for one
332
356
                        if( !_contacts.containsKey( name ) )
333
357
                                return true;
334
358
 
335
359
                        // ok, it exists, so do prompt
336
360
                        _doit._handler.sendMessage( Message.obtain(
337
 
                                        _doit._handler, Doit.MESSAGE_MERGEPROMPT, name ) );
 
361
                                _doit._handler, Doit.MESSAGE_MERGEPROMPT, name ) );
338
362
                        try {
339
363
                                wait();
340
364
                        }
347
371
                        if( _responseExtra == RESPONSEEXTRA_ALWAYS )
348
372
                                _mergeSetting = _response;
349
373
 
350
 
                        // recurse, with out new merge setting
 
374
                        // recurse, with our new merge setting
351
375
                        return isImportRequired( name, _response );
352
376
                }
353
377
 
379
403
                if( ( id = (Long)_contacts.get( contact._name ) ) != null )
380
404
                {
381
405
                        // should we skip this import altogether?
382
 
                        if( _lastMergeDecision == R.id.merge_keep ) return;
 
406
                        if( _lastMergeDecision == Doit.ACTION_KEEP ) return;
383
407
 
384
408
                        // get contact's URI
385
409
                        contactUri = ContentUris.withAppendedId(
386
 
                                        Contacts.People.CONTENT_URI, id );
 
410
                                Contacts.People.CONTENT_URI, id );
387
411
 
388
412
                        // should we destroy the existing contact before importing?
389
 
                        if( _lastMergeDecision == R.id.merge_overwrite ) {
 
413
                        if( _lastMergeDecision == Doit.ACTION_OVERWRITE ) {
390
414
                                _doit.getContentResolver().delete( contactUri, null, null );
391
415
                                contactUri = null;
392
416
 
393
 
                                // upate the UI
394
 
                                _doit._handler.sendEmptyMessage( Doit.MESSAGE_CONTACTOVERWRITTEN );
 
417
                                // update the UI
 
418
                                _doit._handler.sendEmptyMessage(
 
419
                                                Doit.MESSAGE_CONTACTOVERWRITTEN );
395
420
                                uiInformed = true;
396
421
 
397
422
                                // update cache
406
431
                        // create a new contact
407
432
                        values.put( Contacts.People.NAME, contact._name );
408
433
                        contactUri = _doit.getContentResolver().insert(
409
 
                                        Contacts.People.CONTENT_URI, values );
 
434
                                Contacts.People.CONTENT_URI, values );
410
435
                        id = ContentUris.parseId( contactUri );
411
436
                        if( id <= 0 ) return;   // shouldn't happen!
412
437
 
413
 
                        // add them to the "My Contacts" group
414
 
                        Contacts.People.addToGroup(
415
 
                                        _doit.getContentResolver(), id,
416
 
                                        Contacts.Groups.GROUP_MY_CONTACTS );
 
438
                        // try to add them to the "My Contacts" group
 
439
                        try {
 
440
                                Contacts.People.addToMyContactsGroup(
 
441
                                        _doit.getContentResolver(), id );
 
442
                        }
 
443
                        catch( IllegalStateException e ) {
 
444
                                // ignore any failure
 
445
                        }
417
446
 
418
447
                        // update cache
419
448
                        _contacts.put( contact._name, id );
442
471
                Long contactId = ContentUris.parseId( contactUri );
443
472
                Uri contactPhonesUri = Uri.withAppendedPath( contactUri,
444
473
                                Contacts.People.Phones.CONTENT_DIRECTORY );
 
474
                Set< String > phonesKeys = phones.keySet();
445
475
 
446
476
                // add phone numbers
447
 
                Set phonesKeys = phones.keySet();
448
 
                Iterator i = phonesKeys.iterator();
 
477
                Iterator< String > i = phonesKeys.iterator();
449
478
                while( i.hasNext() ) {
450
479
                        ContactData.PhoneData phone = phones.get( i.next() );
451
480
 
468
497
                        if( phone._isPreferred ) values.put( Contacts.Phones.ISPRIMARY, 1 );
469
498
                        _doit.getContentResolver().insert( contactPhonesUri, values );
470
499
                }
 
500
 
 
501
                // now add those phone numbers to the cache to prevent the addition of
 
502
                // duplicate data from another file
 
503
                i = phonesKeys.iterator();
 
504
                while( i.hasNext() ) {
 
505
                        ContactData.PhoneData phone = phones.get( i.next() );
 
506
 
 
507
                        String number = sanitisePhoneNumber( phone._number );
 
508
                        if( number != null ) {
 
509
                                HashSet< String > numbers = _contactNumbers.get( contactId );
 
510
                                if( numbers == null ) {
 
511
                                        _contactNumbers.put( contactId, new HashSet< String >() );
 
512
                                        numbers = _contactNumbers.get( contactId );
 
513
                                }
 
514
                                numbers.add( number );
 
515
                        }
 
516
                }
471
517
        }
472
518
 
473
519
        private void importContactEmails( Uri contactUri,
476
522
                Long contactId = ContentUris.parseId( contactUri );
477
523
                Uri contactContactMethodsUri = Uri.withAppendedPath( contactUri,
478
524
                                Contacts.People.ContactMethods.CONTENT_DIRECTORY );
 
525
                Set< String > emailsKeys = emails.keySet();
479
526
 
480
 
                // add phone numbers
481
 
                Set emailsKeys = emails.keySet();
482
 
                Iterator i = emailsKeys.iterator();
 
527
                // add email addresses
 
528
                Iterator< String > i = emailsKeys.iterator();
483
529
                while( i.hasNext() ) {
484
530
                        ContactData.EmailData email = emails.get( i.next() );
485
531
 
498
544
                        if( email.isPreferred() )
499
545
                                values.put( Contacts.ContactMethods.ISPRIMARY, 1 );
500
546
                        _doit.getContentResolver().insert( contactContactMethodsUri,
501
 
                                        values );
 
547
                                values );
 
548
                }
 
549
 
 
550
                // now add those email addresses to the cache to prevent the addition of
 
551
                // duplicate data from another file
 
552
                i = emailsKeys.iterator();
 
553
                while( i.hasNext() ) {
 
554
                        ContactData.EmailData email = emails.get( i.next() );
 
555
 
 
556
                        String address = sanitiseEmailAddress( email.getAddress() );
 
557
                        if( address != null ) {
 
558
                                HashSet< String > addresses = _contactEmails.get( contactId );
 
559
                                if( addresses == null ) {
 
560
                                        _contactEmails.put( contactId, new HashSet< String >() );
 
561
                                        addresses = _contactEmails.get( contactId );
 
562
                                }
 
563
                                addresses.add( address );
 
564
                        }
502
565
                }
503
566
        }
504
567
 
526
589
                // query and store map of contact names to ids
527
590
                cols = new String[] { Contacts.People._ID, Contacts.People.NAME };
528
591
                cur = _doit.managedQuery( Contacts.People.CONTENT_URI,
529
 
                                cols, null, null, null);
 
592
                        cols, null, null, null);
530
593
                if( cur.moveToFirst() ) {
531
594
                        int idCol = cur.getColumnIndex( Contacts.People._ID );
532
595
                        int nameCol = cur.getColumnIndex( Contacts.People.NAME );
539
602
                cols = new String[] { Contacts.Phones.PERSON_ID,
540
603
                                Contacts.Phones.NUMBER };
541
604
                cur = _doit.managedQuery( Contacts.Phones.CONTENT_URI,
542
 
                                cols, null, null, null);
 
605
                        cols, null, null, null);
543
606
                if( cur.moveToFirst() ) {
544
607
                        int personIdCol = cur.getColumnIndex( Contacts.Phones.PERSON_ID );
545
608
                        int numberCol = cur.getColumnIndex( Contacts.Phones.NUMBER );
566
629
                                new String[] { "" + Contacts.KIND_EMAIL }, null );
567
630
                if( cur.moveToFirst() ) {
568
631
                        int personIdCol = cur.getColumnIndex(
569
 
                                        Contacts.ContactMethods.PERSON_ID );
 
632
                                Contacts.ContactMethods.PERSON_ID );
570
633
                        int addressCol = cur.getColumnIndex(
571
 
                                        Contacts.ContactMethods.DATA );
 
634
                                Contacts.ContactMethods.DATA );
572
635
                        do {
573
636
                                Long id = cur.getLong( personIdCol );
574
637
                                String address = sanitiseEmailAddress(
575
 
                                                cur.getString( addressCol ) );
 
638
                                        cur.getString( addressCol ) );
576
639
                                if( address != null ) {
577
640
                                        HashSet< String > addresses = _contactEmails.get( id );
578
641
                                        if( addresses == null ) {
588
651
        private String sanitisePhoneNumber( String number )
589
652
        {
590
653
                number = number.replaceAll( "[-\\(\\) ]", "" );
591
 
                Pattern p = Pattern.compile( "^\\+?[0-9]+" );
 
654
                Pattern p = Pattern.compile( "^[\\+0-9#*]+" );
592
655
                Matcher m = p.matcher( number );
593
656
                if( m.lookingAt() ) return m.group( 0 );
594
657
                return null;
598
661
        {
599
662
                address = address.trim();
600
663
                Pattern p = Pattern.compile(
601
 
                                "^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
 
664
                        "^[^ @]+@[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?(\\.[a-zA-Z]([-a-zA-Z0-9]*[a-zA-z0-9])?)+$" );
602
665
                Matcher m = p.matcher( address );
603
666
                if( m.matches() ) {
604
667
                        String[] bits = address.split( "@" );