/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/VCFImporter.java

  • Committer: edam
  • Date: 2011-05-02 18:28:24 UTC
  • Revision ID: edam@waxworlds.org-20110502182824-acgdi3qfxfzqgely
- fixed logic for vcard field types (home, work, cell, etc) so it works
- updated NEWS and TODO
- rewrote most of ContactsCache, including a new ContactIdentifier class to identify contacts in the cache and new cache building code
- contacts now identified in the same way that Andoid displays them (by name, or organisation, or number, or email, in that order)
- propper handling and support for organisations and titles
- validation of imported contact now done by Importer, not VcfImporter
- separated sanitisation and normalisation (for cache lookups)
- generacised PhoneData, EmailData and AddressData classes
- ContactData is now aware of primary numbers, emails and organisations (defaults to the first prefrred one seen, or the first one seen where none is preferred)

Show diffs side-by-side

added added

removed removed

1
1
/*
2
2
 * VCFImporter.java
3
3
 *
4
 
 * Copyright (C) 2009 Tim Marston <edam@waxworlds.org>
 
4
 * Copyright (C) 2009 to 2011 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
34
34
import java.nio.ByteBuffer;
35
35
import java.util.ArrayList;
36
36
import java.util.Arrays;
 
37
import java.util.HashMap;
37
38
import java.util.HashSet;
38
39
import java.util.Iterator;
39
40
import java.util.List;
43
44
import java.util.regex.Matcher;
44
45
import java.util.regex.Pattern;
45
46
 
 
47
import org.waxworlds.edam.importcontacts.Importer.ContactData.ExtraDetail;
 
48
 
46
49
import android.content.SharedPreferences;
47
50
import android.provider.Contacts;
48
51
import android.provider.Contacts.PhonesColumns;
330
333
        private class VCard extends ContactData
331
334
        {
332
335
                private final static int NAMELEVEL_NONE = 0;
333
 
                private final static int NAMELEVEL_ORG = 1;
334
 
                private final static int NAMELEVEL_FN = 2;
335
 
                private final static int NAMELEVEL_N = 3;
 
336
                private final static int NAMELEVEL_FN = 1;
 
337
                private final static int NAMELEVEL_N = 2;
336
338
 
337
339
                private final static int MULTILINE_NONE = 0;
338
340
                private final static int MULTILINE_ENCODED = 1; // v2.1 quoted-printable
345
347
                private int _parser_multiline_state = MULTILINE_NONE;
346
348
                private String _parser_current_name_and_params = null;
347
349
                private String _parser_buffered_value_so_far = "";
 
350
                private String _cached_organisation = null;
 
351
                private String _cached_title = null;
348
352
 
349
353
                protected class UnencodeResult
350
354
                {
621
625
                                        parseFN( name_param_parts, complete_value );
622
626
                                else if( name_param_parts[ 0 ].equals( "ORG" ) )
623
627
                                        parseORG( name_param_parts, complete_value );
 
628
                                else if( name_param_parts[ 0 ].equals( "TITLE" ) )
 
629
                                        parseTITLE( name_param_parts, complete_value );
624
630
                                else if( name_param_parts[ 0 ].equals( "TEL" ) )
625
631
                                        parseTEL( name_param_parts, complete_value );
626
632
                                else if( name_param_parts[ 0 ].equals( "EMAIL" ) )
683
689
                }
684
690
 
685
691
                private void parseN( String[] params, String value )
686
 
                        throws ParseException, SkipContactException,
687
 
                        AbortImportException
688
692
                {
689
693
                        // already got a better name?
690
694
                        if( _name_level >= NAMELEVEL_N ) return;
702
706
                        // set name
703
707
                        setName( value );
704
708
                        _name_level = NAMELEVEL_N;
705
 
 
706
 
                        // check now to see if we need to import this contact (to avoid
707
 
                        // parsing the rest of the vCard unnecessarily)
708
 
                        if( !isImportRequired( getName() ) )
709
 
                                throw new SkipContactException();
710
709
                }
711
710
 
712
711
                private void parseFN( String[] params, String value )
713
 
                        throws ParseException, SkipContactException
714
712
                {
715
713
                        // already got a better name?
716
714
                        if( _name_level >= NAMELEVEL_FN ) return;
721
719
                }
722
720
 
723
721
                private void parseORG( String[] params, String value )
724
 
                        throws ParseException, SkipContactException
725
722
                {
726
 
                        // already got a better name?
727
 
                        if( _name_level >= NAMELEVEL_ORG ) return;
728
 
 
729
723
                        // get org parts
730
724
                        String[] org_parts = splitValueBySemicolon( value );
731
 
 
732
 
                        // build name
733
 
                        if( org_parts.length > 1 && org_parts[ 0 ].length() == 0 )
734
 
                                value = org_parts[ 1 ];
735
 
                        else if( org_parts.length > 1 && org_parts[ 1 ].length() > 0 )
736
 
                                value = org_parts[ 0 ] + ", " + org_parts[ 1 ];
737
 
                        else
738
 
                                value = org_parts[ 0 ];
739
 
 
740
 
                        // set name
741
 
                        setName( value );
742
 
                        _name_level = NAMELEVEL_ORG;
 
725
                        if( org_parts == null || org_parts.length < 1 ) return;
 
726
 
 
727
                        // build organisation name
 
728
                        StringBuilder builder = new StringBuilder(
 
729
                                String.valueOf( org_parts[ 0 ] ) );
 
730
                        for( int a = 1; a < org_parts.length; a++ )
 
731
                                builder.append( ", " ).append( org_parts[ a ] );
 
732
                        String organisation = builder.toString();
 
733
 
 
734
                        // set organisation name (using a title we've previously found)
 
735
                        addOrganisation( organisation, _cached_title, true );
 
736
 
 
737
                        // if we've not previously found a title, store this organisation
 
738
                        // name (we'll need it when we find a title to update the
 
739
                        // organisation, by name), else if we *have* previously found a
 
740
                        // title, clear it (since we just used it)
 
741
                        if( _cached_title == null )
 
742
                                _cached_organisation = organisation;
 
743
                        else
 
744
                                _cached_title = null;
 
745
                }
 
746
 
 
747
                private void parseTITLE( String[] params, String value )
 
748
                {
 
749
                        // if we previously had an organisation, look it up and append this
 
750
                        // title to it
 
751
                        if( _cached_organisation != null && hasOrganisations() ) {
 
752
                                HashMap< String, ExtraDetail > datas = getOrganisations();
 
753
                                ExtraDetail detail = datas.get( _cached_organisation );
 
754
                                if( detail != null )
 
755
                                        detail.setExtra( value );
 
756
                        }
 
757
 
 
758
                        // same as when handling organisation, if we've not previously found
 
759
                        // an organisation we store this title, else we clear it (since we
 
760
                        // just appended this title to it)
 
761
                        if( _cached_organisation == null )
 
762
                                _cached_title = value;
 
763
                        else
 
764
                                _cached_organisation = null;
743
765
                }
744
766
 
745
767
                private void parseTEL( String[] params, String value )
746
 
                        throws ParseException
747
768
                {
748
769
                        if( value.length() == 0 ) return;
749
770
 
753
774
 
754
775
                        // here's the logic...
755
776
                        boolean preferred = types.contains( "PREF" );
756
 
                        int type = PhonesColumns.TYPE_MOBILE;
757
 
                        if( types.contains( "VOICE" ) )
758
 
                                if( types.contains( "WORK" ) )
759
 
                                        type = PhonesColumns.TYPE_WORK;
760
 
                                else
761
 
                                        type = PhonesColumns.TYPE_HOME;
762
 
                        else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
763
 
                                type = PhonesColumns.TYPE_MOBILE;
 
777
                        int type;
764
778
                        if( types.contains( "FAX" ) )
765
779
                                if( types.contains( "HOME" ) )
766
780
                                        type = PhonesColumns.TYPE_FAX_HOME;
767
781
                                else
768
782
                                        type = PhonesColumns.TYPE_FAX_WORK;
769
 
                        if( types.contains( "PAGER" ) )
 
783
                        else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
 
784
                                type = PhonesColumns.TYPE_MOBILE;
 
785
                        else if( types.contains( "PAGER" ) )
770
786
                                type = PhonesColumns.TYPE_PAGER;
 
787
                        else if( types.contains( "WORK" ) )
 
788
                                type = PhonesColumns.TYPE_WORK;
 
789
                        else
 
790
                                type = PhonesColumns.TYPE_HOME;
771
791
 
772
792
                        // add phone number
773
 
                        addPhone( value, type, preferred );
 
793
                        addNumber( value, type, preferred );
774
794
                }
775
795
 
776
796
                public void parseEMAIL( String[] params, String value )
777
 
                        throws ParseException
778
797
                {
779
798
                        if( value.length() == 0 ) return;
780
799
 
783
802
 
784
803
                        // add email address
785
804
                        boolean preferred = types.contains( "PREF" );
 
805
                        int type;
786
806
                        if( types.contains( "WORK" ) )
787
 
                                addEmail( value, Contacts.ContactMethods.TYPE_WORK, preferred );
 
807
                                type = Contacts.ContactMethods.TYPE_WORK;
788
808
                        else
789
 
                                addEmail( value, Contacts.ContactMethods.TYPE_HOME, preferred );
 
809
                                type = Contacts.ContactMethods.TYPE_HOME;
 
810
 
 
811
                        addEmail( value, type, preferred );
790
812
                }
791
813
 
792
814
                private void parseADR( String[] params, String value )
793
 
                        throws ParseException, SkipContactException
794
815
                {
795
816
                        // get address parts
796
817
                        String[] adr_parts = splitValueBySemicolon( value );
806
827
                                "PREF", "WORK", "HOME", "INTERNET" ) );
807
828
 
808
829
                        // add address
 
830
                        int type;
809
831
                        if( types.contains( "WORK" ) )
810
 
                                addAddress( value, Contacts.ContactMethods.TYPE_WORK );
 
832
                                type = Contacts.ContactMethods.TYPE_WORK;
811
833
                        else
812
 
                                addAddress( value, Contacts.ContactMethods.TYPE_HOME);
 
834
                                type = Contacts.ContactMethods.TYPE_HOME;
 
835
 
 
836
                        addAddress( value, type );
813
837
                }
814
838
 
815
839
                public void finaliseParsing()
820
844
                        if( _version == null && _buffers != null )
821
845
                                throw new ParseException( R.string.error_vcf_malformed );
822
846
 
823
 
                        //  missing name properties?
824
 
                        if( _name_level == NAMELEVEL_NONE )
825
 
                                throw new ParseException( R.string.error_vcf_noname );
826
 
 
827
 
                        // check if we should import this one? If we've already got an 'N'-
828
 
                        // type name, this will already have been done by parseN() so we
829
 
                        // mustn't do this here (or it could prompt twice!)
830
 
                        if( _name_level < NAMELEVEL_N && !isImportRequired( getName() ) )
831
 
                                throw new SkipContactException();
 
847
                        // check if we should import this contact
 
848
                        try {
 
849
                                if( !isImportRequired( this ) )
 
850
                                        throw new SkipContactException();
 
851
                        }
 
852
                        catch( ContactNeedsMoreInfoException e ) {
 
853
                                throw new ParseException( R.string.error_vcf_notenoughinfo );
 
854
                        }
832
855
                }
833
856
 
834
857
                private String checkParam( String[] params, String name )