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

21
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
22
 */
23
23
 
24
 
package org.waxworlds.edam.importcontacts;
 
24
package org.waxworlds.importcontacts;
25
25
 
26
26
import java.io.BufferedReader;
27
27
import java.io.File;
28
 
import java.io.FileInputStream;
29
28
import java.io.FileNotFoundException;
30
29
import java.io.FileReader;
31
30
import java.io.FilenameFilter;
32
31
import java.io.IOException;
33
32
import java.io.UnsupportedEncodingException;
34
 
import java.nio.ByteBuffer;
35
33
import java.util.Arrays;
36
34
import java.util.HashSet;
37
 
import java.util.Iterator;
38
35
import java.util.List;
39
36
import java.util.Set;
40
37
import java.util.Vector;
41
38
import java.util.regex.Matcher;
42
39
import java.util.regex.Pattern;
43
 
import java.util.NoSuchElementException;
44
 
import java.lang.UnsupportedOperationException;
 
40
 
 
41
import org.waxworlds.importcontacts.Importer.AbortImportException;
45
42
 
46
43
import android.content.SharedPreferences;
47
44
import android.provider.Contacts;
70
67
                try
71
68
                {
72
69
                        // open directory
73
 
                        String path = "/sdcard" + prefs.getString( "location", "/" );
74
 
                        File file = new File( path );
75
 
                        if( !file.exists() )
 
70
                        String location = prefs.getString( "location", "" );
 
71
                        File dir = new File( location );
 
72
                        if( !dir.exists() || !dir.isDirectory() )
76
73
                                showError( R.string.error_locationnotfound );
77
74
 
78
 
                        // directory, or file?
79
 
                        if( file.isDirectory() )
80
 
                        {
81
 
                                // get files
82
 
                                class VCardFilter implements FilenameFilter {
83
 
                                        public boolean accept( File dir, String name ) {
84
 
                                                return name.toLowerCase().endsWith( ".vcf" );
85
 
                                        }
86
 
                                }
87
 
                                files = file.listFiles( new VCardFilter() );
88
 
                        }
89
 
                        else
90
 
                        {
91
 
                                // use just this file
92
 
                                files = new File[ 1 ];
93
 
                                files[ 0 ] = file;
94
 
                        }
 
75
                        // get files
 
76
                        class VCardFilter implements FilenameFilter {
 
77
                            public boolean accept( File dir, String name ) {
 
78
                                return name.toLowerCase().endsWith( ".vcf" );
 
79
                            }
 
80
                        }
 
81
                        files = dir.listFiles( new VCardFilter() );
95
82
                }
96
83
                catch( SecurityException e ) {
97
84
                        showError( R.string.error_locationpermissions );
123
110
                {
124
111
                        // open file
125
112
                        BufferedReader reader = new BufferedReader(
126
 
                                new FileReader( file ) );
 
113
                                        new FileReader( file ) );
127
114
 
128
115
                        // read
129
116
                        String line;
132
119
                        {
133
120
                                if( !inVCard ) {
134
121
                                        // look for vcard beginning
135
 
                                        if( line.matches( "^BEGIN:VCARD" ) ) {
 
122
                                        if( line.matches( "^BEGIN[ \\t]*:[ \\t]*VCARD" ) ) {
136
123
                                                inVCard = true;
137
124
                                                _vCardCount++;
138
125
                                        }
139
126
                                }
140
 
                                else if( line.matches( "^END:VCARD" ) )
 
127
                                else if( line.matches( "^END[ \\t]*:[ \\t]*VCARD" ) )
141
128
                                        inVCard = false;
142
129
                        }
143
130
 
144
131
                }
145
132
                catch( FileNotFoundException e ) {
146
 
                        showError( getText( R.string.error_filenotfound ) +
147
 
                                file.getName() );
 
133
                        showError( getText( R.string.error_filenotfound ) + file.getName() );
148
134
                }
149
135
                catch( IOException e ) {
150
136
                        showError( getText( R.string.error_ioerror ) + file.getName() );
153
139
 
154
140
        private void importVCardFile( File file ) throws AbortImportException
155
141
        {
156
 
                // check file is good
157
 
                if( !file.exists() )
158
 
                        showError( getText( R.string.error_filenotfound ) +
159
 
                                file.getName() );
160
 
                if( file.length() == 0 )
161
 
                        showError( getText( R.string.error_fileisempty ) +
162
 
                                file.getName() );
163
 
 
164
142
                try
165
143
                {
166
 
                        // open/read file
167
 
                        FileInputStream istream = new FileInputStream( file );
168
 
                        byte[] content = new byte[ (int)file.length() ];
169
 
                        istream.read( content );
170
 
 
171
 
                        // import
172
 
                        importVCardFileContent( content, file.getName() );
 
144
                        // open file
 
145
                        BufferedReader reader = new BufferedReader(
 
146
                                        new FileReader( file ) );
 
147
 
 
148
                        // read
 
149
                        StringBuffer content = new StringBuffer();
 
150
                        String line;
 
151
                        while( ( line = reader.readLine() ) != null )
 
152
                                content.append( line ).append( "\n" );
 
153
 
 
154
                        importVCardFileContent( content.toString(), file.getName() );
173
155
                }
174
156
                catch( FileNotFoundException e ) {
175
 
                        showError( getText( R.string.error_filenotfound ) +
176
 
                                file.getName() );
 
157
                        showError( getText( R.string.error_filenotfound ) + file.getName() );
177
158
                }
178
159
                catch( IOException e ) {
179
160
                        showError( getText( R.string.error_ioerror ) + file.getName() );
180
161
                }
181
162
        }
182
163
 
183
 
        private void importVCardFileContent( byte[] content, String fileName )
184
 
                throws AbortImportException
 
164
        private void importVCardFileContent( String content, String fileName )
 
165
                        throws AbortImportException
185
166
        {
186
 
                // go through lines
 
167
                // unfold RFC2425 section 5.8.1 folded lines, except that we must also
 
168
                // handle embedded Quoted-Printable encodings that have a trailing '='.
 
169
                // So we remove these first before doing RFC2425 unfolding.
 
170
                content = content.replaceAll( "=\n[ \\t]", "" )
 
171
                                .replaceAll( "\n[ \\t]", "" );
 
172
 
 
173
                // get lines and parse them
 
174
                String[] lines = content.split( "\n" );
187
175
                VCard vCard = null;
188
 
                ContentLineIterator cli = new ContentLineIterator( content );
189
 
                while( cli.hasNext() )
 
176
                for( int i = 0; i < lines.length; i++ )
190
177
                {
191
 
                        ByteBuffer buffer = cli.next();
192
 
 
193
 
                        // get a US-ASCII version of the line for processing
194
 
                        String line;
195
 
                        try {
196
 
                                line = new String( buffer.array(), buffer.position(),
197
 
                                        buffer.limit() - buffer.position(), "US-ASCII" );
198
 
                        }
199
 
                        catch( UnsupportedEncodingException e ) {
200
 
                                // we know US-ASCII is supported, so appease the compiler...
201
 
                                line = "";
202
 
                        }
 
178
                        String line = lines[ i ];
203
179
 
204
180
                        if( vCard == null ) {
205
181
                                // look for vcard beginning
206
 
                                if( line.matches( "^BEGIN:VCARD" ) ) {
 
182
                                if( line.matches( "^BEGIN[ \\t]*:[ \\t]*VCARD" ) ) {
207
183
                                        setProgress( ++_progress );
208
184
                                        vCard = new VCard();
209
185
                                }
210
186
                        }
211
187
                        else {
212
188
                                // look for vcard content or ending
213
 
                                if( line.matches( "^END:VCARD" ) )
 
189
                                if( line.matches( "^END[ \\t]*:[ \\t]*VCARD" ) )
214
190
                                {
215
191
                                        // store vcard and do away with it
216
192
                                        try {
220
196
                                        catch( VCard.ParseException e ) {
221
197
                                                skipContact();
222
198
                                                if( !showContinue(
223
 
                                                        getText( R.string.error_vcf_parse ).toString()
224
 
                                                        + fileName + "\n" + e.getMessage() ) )
225
 
                                                {
 
199
                                                                getText( R.string.error_vcf_parse ).toString()
 
200
                                                                + fileName + "\n" + e.getMessage() ) )
226
201
                                                        finish( ACTION_ABORT );
227
 
                                                }
228
202
                                        }
229
203
                                        catch( VCard.SkipContactException e ) {
230
204
                                                skipContact();
236
210
                                {
237
211
                                        // try giving the line to the vcard
238
212
                                        try {
239
 
                                                vCard.parseLine( buffer, line,
240
 
                                                        cli.doesNextLineLookFolded() );
 
213
                                                vCard.parseLine( line );
241
214
                                        }
242
215
                                        catch( VCard.ParseException e ) {
243
216
                                                skipContact();
244
217
                                                if( !showContinue(
245
 
                                                        getText( R.string.error_vcf_parse ).toString()
246
 
                                                        + fileName + "\n" + e.getMessage() ) )
247
 
                                                {
 
218
                                                                getText( R.string.error_vcf_parse ).toString()
 
219
                                                                + fileName + "\n" + e.getMessage() ) )
248
220
                                                        finish( ACTION_ABORT );
249
 
                                                }
250
221
 
251
222
                                                // although we're continuing, we still need to abort
252
223
                                                // this vCard. Further lines will be ignored until we
264
235
                }
265
236
        }
266
237
 
267
 
        class ContentLineIterator implements Iterator< ByteBuffer >
268
 
        {
269
 
                protected byte[] _content = null;
270
 
                protected int _pos = 0;
271
 
 
272
 
                public ContentLineIterator( byte[] content )
273
 
                {
274
 
                        _content = content;
275
 
                }
276
 
 
277
 
                @Override
278
 
                public boolean hasNext()
279
 
                {
280
 
                        return _pos < _content.length;
281
 
                }
282
 
 
283
 
                @Override
284
 
                public ByteBuffer next()
285
 
                {
286
 
                        int initial_pos = _pos;
287
 
 
288
 
                        // find newline
289
 
                        for( ; _pos < _content.length; _pos++ )
290
 
                                if( _content[ _pos ] == '\n' )
291
 
                                {
292
 
                                        // adjust for a \r preceding the \n
293
 
                                        int to = ( _pos > 0 && _content[ _pos - 1 ] == '\r' &&
294
 
                                                _pos > initial_pos )? _pos - 1 : _pos;
295
 
                                        _pos++;
296
 
                                        return ByteBuffer.wrap( _content, initial_pos,
297
 
                                                to - initial_pos );
298
 
                                }
299
 
 
300
 
                        // we didn't find one, but were there bytes left?
301
 
                        if( _pos != initial_pos ) {
302
 
                                int to = _pos;
303
 
                                _pos++;
304
 
                                return ByteBuffer.wrap( _content, initial_pos,
305
 
                                        to - initial_pos );
306
 
                        }
307
 
 
308
 
                        // no bytes left
309
 
                        throw new NoSuchElementException();
310
 
                }
311
 
 
312
 
                @Override
313
 
                public void remove()
314
 
                {
315
 
                        throw new UnsupportedOperationException();
316
 
                }
317
 
 
318
 
                /**
319
 
                 * Does the next line, if there is one, look like it should be folded
320
 
                 * onto the end of this one?
321
 
                 * @return
322
 
                 */
323
 
                public boolean doesNextLineLookFolded()
324
 
                {
325
 
                        return _pos > 0 && _pos < _content.length &&
326
 
                                _content[ _pos - 1 ] == '\n' && _content[ _pos ] == ' ';
327
 
                }
328
 
        }
329
 
 
330
238
        private class VCard extends ContactData
331
239
        {
332
240
                private final static int NAMELEVEL_NONE = 0;
335
243
                private final static int NAMELEVEL_N = 3;
336
244
 
337
245
                private String _version = null;
338
 
                private Vector< ByteBuffer > _buffers = null;
339
 
                private int _name_level = NAMELEVEL_NONE;
340
 
                private boolean _parser_in_encoded_multiline = false;
341
 
                private boolean _parser_in_folded_multiline = false;
342
 
                private String _parser_current_name_and_params = null;
343
 
                private String _parser_buffered_value_so_far = "";
344
 
 
345
 
                protected class UnencodeResult
346
 
                {
347
 
                        private boolean _another_line_required;
348
 
                        private ByteBuffer _buffer;
349
 
 
350
 
                        public UnencodeResult( boolean another_line_required,
351
 
                                ByteBuffer buffer )
352
 
                        {
353
 
                                _another_line_required = another_line_required;
354
 
                                _buffer = buffer;
355
 
                        }
356
 
 
357
 
                        public boolean isAnotherLineRequired()
358
 
                        {
359
 
                                return _another_line_required;
360
 
                        }
361
 
 
362
 
                        public ByteBuffer getBuffer()
363
 
                        {
364
 
                                return _buffer;
365
 
                        }
366
 
                }
367
 
 
368
 
                @SuppressWarnings("serial")
 
246
                private Vector< String > _lines = null;
 
247
                private int _nameLevel = NAMELEVEL_NONE;
 
248
 
369
249
                protected class ParseException extends Exception
370
250
                {
371
 
                        @SuppressWarnings("unused")
372
251
                        public ParseException( String error )
373
252
                        {
374
253
                                super( error );
380
259
                        }
381
260
                }
382
261
 
383
 
                @SuppressWarnings("serial")
384
262
                protected class SkipContactException extends Exception { }
385
263
 
386
 
                private String extractCollonPartFromLine( ByteBuffer buffer,
387
 
                        String line, boolean former )
388
 
                {
389
 
                        String ret = null;
390
 
 
391
 
                        // get a US-ASCII version of the line for processing, unless we were
392
 
                        // supplied with one
393
 
                        if( line == null ) {
394
 
                                try {
395
 
                                        line = new String( buffer.array(), buffer.position(),
396
 
                                                buffer.limit() - buffer.position(), "US-ASCII" );
397
 
                                }
398
 
                                catch( UnsupportedEncodingException e ) {
399
 
                                        // we know US-ASCII is supported, so appease the compiler...
400
 
                                        line = "";
401
 
                                }
402
 
                        }
403
 
 
404
 
                        // split line into name and value parts and check to make sure we
405
 
                        // only got 2 parts and that the first part is not zero in length
406
 
                        String[] parts = line.split( ":", 2 );
407
 
                        if( parts.length == 2 && parts[ 0 ].length() > 0 )
408
 
                                ret = parts[ former? 0 : 1 ];
409
 
 
410
 
                        return ret;
411
 
                }
412
 
 
413
 
                private String extractNameAndParamsFromLine( ByteBuffer buffer,
414
 
                        String line )
415
 
                {
416
 
                        return extractCollonPartFromLine( buffer, line, true );
417
 
                }
418
 
 
419
 
                private String extractValueFromLine( ByteBuffer buffer, String line )
420
 
                {
421
 
                        return extractCollonPartFromLine( buffer, line, false );
422
 
                }
423
 
 
424
 
                public void parseLine( ByteBuffer buffer, String line,
425
 
                        boolean next_line_looks_folded )
426
 
                        throws ParseException, SkipContactException,
427
 
                        AbortImportException
428
 
                {
429
 
                        // do we have a version yet?
 
264
                public void parseLine( String line )
 
265
                                throws ParseException, SkipContactException,
 
266
                                AbortImportException
 
267
                {
 
268
                        // get property halves
 
269
                        String[] props = line.split( ":" );
 
270
                        for( int i = 0; i < props.length; i++ )
 
271
                                props[ i ] = props[ i ].trim();
 
272
                        if( props.length < 2 ||
 
273
                                        props[ 0 ].length() < 1 || props[ 1 ].length() < 1 )
 
274
                                throw new ParseException( R.string.error_vcf_malformed );
 
275
 
430
276
                        if( _version == null )
431
277
                        {
432
 
                                // tentatively get name and params from line
433
 
                                String name_and_params =
434
 
                                        extractNameAndParamsFromLine( buffer, line );
435
 
 
436
 
                                // is it a version line?
437
 
                                if( name_and_params != null &&
438
 
                                        name_and_params.equals( "VERSION" ) )
 
278
                                if( props[ 0 ].equals( "VERSION" ) )
439
279
                                {
440
 
                                        // yes, get it!
441
 
                                        String value = extractValueFromLine( buffer, line );
442
 
                                        if( !value.equals( "2.1" ) && !value.equals( "3.0" ) )
 
280
                                        // get version
 
281
                                        if( !props[ 1 ].equals( "2.1" ) &&
 
282
                                                        !props[ 1 ].equals( "3.0" ) )
443
283
                                                throw new ParseException( R.string.error_vcf_version );
444
 
                                        _version = value;
 
284
                                        _version = props[ 1 ];
445
285
 
446
 
                                        // parse any buffers we've been accumulating while we waited
447
 
                                        // for a version
448
 
                                        if( _buffers != null )
449
 
                                                for( int i = 0; i < _buffers.size(); i++ )
450
 
                                                        parseLine( _buffers.get( i ), null,
451
 
                                                                i + 1 < _buffers.size() &&
452
 
                                                                _buffers.get( i + 1 ).hasRemaining() &&
453
 
                                                                _buffers.get( i + 1 ).get(
454
 
                                                                        _buffers.get( i + 1 ).position() ) == ' ' );
455
 
                                        _buffers = null;
 
286
                                        // parse any other lines we've accumulated so far
 
287
                                        if( _lines != null )
 
288
                                                for( int i = 0; i < _lines.size(); i++ )
 
289
                                                        parseLine( _lines.get( i ) );
 
290
                                        _lines = null;
456
291
                                }
457
292
                                else
458
293
                                {
459
 
                                        // no, so stash this line till we get a version
460
 
                                        if( _buffers == null )
461
 
                                                _buffers = new Vector< ByteBuffer >();
462
 
                                        _buffers.add( buffer );
 
294
                                        // stash this line till we have a version
 
295
                                        if( _lines == null )
 
296
                                                _lines = new Vector< String >();
 
297
                                        _lines.add( line );
463
298
                                }
464
299
                        }
465
300
                        else
466
301
                        {
467
 
                                // name and params and the position in the buffer where the
468
 
                                // "value" part of the line start
469
 
                                String name_and_params;
470
 
                                int pos;
471
 
 
472
 
                                if( _parser_in_encoded_multiline ||
473
 
                                        _parser_in_folded_multiline )
474
 
                                {
475
 
                                        // if we're currently in a multi-line value, use the stored
476
 
                                        // property name and parameters
477
 
                                        name_and_params = _parser_current_name_and_params;
478
 
 
479
 
                                        pos = buffer.position();
480
 
 
481
 
                                        // for folded multi-lines, skip the single space at the
482
 
                                        // start of the next line
483
 
                                        if( _parser_in_folded_multiline )
484
 
                                                pos++;
485
 
 
486
 
                                        // else, this must be an encoded multi-line, so skip any
487
 
                                        // whitespace we find at the start of the next line
488
 
                                        else
489
 
                                                while( pos < buffer.limit() && (
490
 
                                                        buffer.get( pos ) == ' ' ||
491
 
                                                        buffer.get( pos ) == '\t' ) )
492
 
                                                {
493
 
                                                        pos++;
494
 
                                                }
495
 
                                }
496
 
                                else
497
 
                                {
498
 
                                        // get name and params from line, and since we're not
499
 
                                        // parsing a subsequent line in a multi-line, this should
500
 
                                        // not fail, or it's an error
501
 
                                        name_and_params =
502
 
                                                extractNameAndParamsFromLine( buffer, line );
503
 
                                        if( name_and_params == null )
504
 
                                                throw new ParseException(
505
 
                                                        R.string.error_vcf_malformed );
506
 
 
507
 
                                        // calculate how many chars to skip from beginning of line
508
 
                                        // so we skip the property "name:" part
509
 
                                        pos = buffer.position() + name_and_params.length() + 1;
510
 
 
511
 
                                        // reset the saved multi-line state
512
 
                                        _parser_current_name_and_params = name_and_params;
513
 
                                        _parser_buffered_value_so_far = "";
514
 
                                }
515
 
 
516
 
                                // get value from buffer, as raw bytes
517
 
                                ByteBuffer value;
518
 
                                value = ByteBuffer.wrap( buffer.array(), pos,
519
 
                                        buffer.limit() - pos );
520
 
 
521
302
                                // get parameter parts
522
 
                                String[] name_param_parts = name_and_params.split( ";", -1 );
523
 
                                for( int i = 0; i < name_param_parts.length; i++ )
524
 
                                        name_param_parts[ i ] = name_param_parts[ i ].trim();
525
 
 
526
 
                                // parse encoding parameter
527
 
                                String encoding = checkParam( name_param_parts, "ENCODING" );
528
 
                                if( encoding != null ) encoding = encoding.toUpperCase();
529
 
                                if( encoding != null && !encoding.equals( "8BIT" ) &&
530
 
                                        !encoding.equals( "QUOTED-PRINTABLE" ) )
531
 
                                        //&& !encoding.equals( "BASE64" ) )
532
 
                                {
533
 
                                        throw new ParseException( R.string.error_vcf_encoding );
534
 
                                }
535
 
 
536
 
                                // parse charset parameter
537
 
                                String charset = checkParam( name_param_parts, "CHARSET" );
538
 
                                if( charset != null ) charset = charset.toUpperCase();
539
 
                                if( charset != null && !charset.equals( "US-ASCII" ) &&
540
 
                                        !charset.equals( "ASCII" ) &&
541
 
                                        !charset.equals( "UTF-8" ) )
542
 
                                {
543
 
                                        throw new ParseException( R.string.error_vcf_charset );
544
 
                                }
545
 
 
546
 
                                // do unencoding (or default to a fake unencoding result with
547
 
                                // the raw string)
548
 
                                UnencodeResult unencoding_result = null;
549
 
                                if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
550
 
                                        unencoding_result = unencodeQuotedPrintable( value );
551
 
//                              else if( encoding != null && encoding.equals( "BASE64" ) )
552
 
//                                      unencoding_result = unencodeBase64( props[ 1 ], charset );
553
 
                                if( unencoding_result != null ) {
554
 
                                        value = unencoding_result.getBuffer();
555
 
                                        _parser_in_encoded_multiline =
556
 
                                                unencoding_result.isAnotherLineRequired();
557
 
                                }
558
 
 
559
 
                                // convert 8-bit ASCII charset to US-ASCII
560
 
                                if( charset == null || charset.equals( "ASCII" ) ) {
561
 
                                        value = transcodeAsciiToUtf8( value );
562
 
                                        charset = "UTF-8";
563
 
                                }
564
 
 
565
 
                                // process charset
566
 
                                String string_value;
567
 
                                try {
568
 
                                        string_value = new String( value.array(), value.position(),
569
 
                                                value.limit() - value.position(), charset );
570
 
                                } catch( UnsupportedEncodingException e ) {
571
 
                                        throw new ParseException( R.string.error_vcf_charset );
572
 
                                }
573
 
 
574
 
                                // now we know whether we're in an encoding multi-line,
575
 
                                // determine if we're in a v3 folded multi-line or not
576
 
                                _parser_in_folded_multiline = !_parser_in_encoded_multiline &&
577
 
                                        _version.equals( "3.0" ) && next_line_looks_folded;
578
 
 
579
 
                                // handle multi-line requests
580
 
                                if( _parser_in_encoded_multiline ||
581
 
                                        _parser_in_folded_multiline )
582
 
                                {
583
 
                                        _parser_buffered_value_so_far += string_value;
584
 
                                        return;
585
 
                                }
586
 
 
587
 
                                // add on buffered multi-line content
588
 
                                String complete_value =
589
 
                                        _parser_buffered_value_so_far + string_value;
590
 
 
591
 
                                // ignore empty values
592
 
                                if( complete_value.length() < 1 ) return;
 
303
                                String[] params = props[ 0 ].split( ";" );
 
304
                                for( int i = 0; i < params.length; i++ )
 
305
                                        params[ i ] = params[ i ].trim();
593
306
 
594
307
                                // parse some properties
595
 
                                if( name_param_parts[ 0 ].equals( "N" ) )
596
 
                                        parseN( name_param_parts, complete_value );
597
 
                                else if( name_param_parts[ 0 ].equals( "FN" ) )
598
 
                                        parseFN( name_param_parts, complete_value );
599
 
                                else if( name_param_parts[ 0 ].equals( "ORG" ) )
600
 
                                        parseORG( name_param_parts, complete_value );
601
 
                                else if( name_param_parts[ 0 ].equals( "TEL" ) )
602
 
                                        parseTEL( name_param_parts, complete_value );
603
 
                                else if( name_param_parts[ 0 ].equals( "EMAIL" ) )
604
 
                                        parseEMAIL( name_param_parts, complete_value );
 
308
                                if( params[ 0 ].equals( "N" ) )
 
309
                                        parseN( params, props[ 1 ] );
 
310
                                else if( params[ 0 ].equals( "FN" ) )
 
311
                                        parseFN( params, props[ 1 ] );
 
312
                                else if( params[ 0 ].equals( "ORG" ) )
 
313
                                        parseORG( params, props[ 1 ] );
 
314
                                else if( params[ 0 ].equals( "TEL" ) )
 
315
                                        parseTEL( params, props[ 1 ] );
 
316
                                else if( params[ 0 ].equals( "EMAIL" ) )
 
317
                                        parseEMAIL( params, props[ 1 ] );
605
318
                        }
606
319
                }
607
320
 
608
321
                private void parseN( String[] params, String value )
609
 
                        throws ParseException, SkipContactException,
610
 
                        AbortImportException
 
322
                                throws ParseException, SkipContactException,
 
323
                                AbortImportException
611
324
                {
612
325
                        // already got a better name?
613
 
                        if( _name_level >= NAMELEVEL_N ) return;
 
326
                        if( _nameLevel >= NAMELEVEL_N ) return;
614
327
 
615
328
                        // get name parts
616
 
                        String[] name_parts = value.split( ";" );
617
 
                        for( int i = 0; i < name_parts.length; i++ )
618
 
                                name_parts[ i ] = name_parts[ i ].trim();
 
329
                        String[] nameparts = value.split( ";" );
 
330
                        for( int i = 0; i < nameparts.length; i++ )
 
331
                                nameparts[ i ] = nameparts[ i ].trim();
619
332
 
620
333
                        // build name
621
334
                        value = "";
622
 
                        if( name_parts.length > 1 && name_parts[ 1 ].length() > 0 )
623
 
                                value += name_parts[ 1 ];
624
 
                        if( name_parts.length > 0 && name_parts[ 0 ].length() > 0 )
625
 
                                value += ( value.length() == 0? "" : " " ) + name_parts[ 0 ];
 
335
                        if( nameparts.length > 1 && nameparts[ 1 ].length() > 0 )
 
336
                                value += nameparts[ 1 ];
 
337
                        if( nameparts[ 0 ].length() > 0 )
 
338
                                value += ( value.length() == 0? "" : " " ) + nameparts[ 0 ];
626
339
 
627
340
                        // set name
628
 
                        setName( value );
629
 
                        _name_level = NAMELEVEL_N;
 
341
                        setName( undoCharsetAndEncoding( params, value ) );
 
342
                        _nameLevel = NAMELEVEL_N;
630
343
 
631
344
                        // check now to see if we need to import this contact (to avoid
632
345
                        // parsing the rest of the vCard unnecessarily)
635
348
                }
636
349
 
637
350
                private void parseFN( String[] params, String value )
638
 
                        throws ParseException, SkipContactException
 
351
                                throws ParseException, SkipContactException
639
352
                {
640
353
                        // already got a better name?
641
 
                        if( _name_level >= NAMELEVEL_FN ) return;
 
354
                        if( _nameLevel >= NAMELEVEL_FN ) return;
642
355
 
643
356
                        // set name
644
 
                        setName( value );
645
 
                        _name_level = NAMELEVEL_FN;
 
357
                        setName( undoCharsetAndEncoding( params, value ) );
 
358
                        _nameLevel = NAMELEVEL_FN;
646
359
                }
647
360
 
648
361
                private void parseORG( String[] params, String value )
649
 
                        throws ParseException, SkipContactException
 
362
                                throws ParseException, SkipContactException
650
363
                {
651
364
                        // already got a better name?
652
 
                        if( _name_level >= NAMELEVEL_ORG ) return;
 
365
                        if( _nameLevel >= NAMELEVEL_ORG ) return;
653
366
 
654
367
                        // get org parts
655
 
                        String[] org_parts = value.split( ";" );
656
 
                        for( int i = 0; i < org_parts.length; i++ )
657
 
                                org_parts[ i ] = org_parts[ i ].trim();
 
368
                        String[] orgparts = value.split( ";" );
 
369
                        for( int i = 0; i < orgparts.length; i++ )
 
370
                                orgparts[ i ] = orgparts[ i ].trim();
658
371
 
659
372
                        // build name
660
 
                        if( org_parts.length > 1 && org_parts[ 0 ].length() == 0 )
661
 
                                value = org_parts[ 1 ];
 
373
                        if( orgparts[ 0 ].length() == 0 && orgparts.length > 1 )
 
374
                                value = orgparts[ 1 ];
662
375
                        else
663
 
                                value = org_parts[ 0 ];
 
376
                                value = orgparts[ 0 ];
664
377
 
665
378
                        // set name
666
 
                        setName( value );
667
 
                        _name_level = NAMELEVEL_ORG;
 
379
                        setName( undoCharsetAndEncoding( params, value ) );
 
380
                        _nameLevel = NAMELEVEL_ORG;
668
381
                }
669
382
 
670
383
                private void parseTEL( String[] params, String value )
671
 
                        throws ParseException
 
384
                                throws ParseException
672
385
                {
673
386
                        if( value.length() == 0 ) return;
674
387
 
675
388
                        Set< String > types = extractTypes( params, Arrays.asList(
676
 
                                "PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
677
 
                                "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
 
389
                                        "PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
 
390
                                        "PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
678
391
 
679
392
                        // here's the logic...
680
393
                        boolean preferred = types.contains( "PREF" );
681
 
                        int type = PhonesColumns.TYPE_MOBILE;
682
394
                        if( types.contains( "VOICE" ) )
683
395
                                if( types.contains( "WORK" ) )
684
 
                                        type = PhonesColumns.TYPE_WORK;
 
396
                                        addPhone( value, PhonesColumns.TYPE_WORK, preferred );
685
397
                                else
686
 
                                        type = PhonesColumns.TYPE_HOME;
 
398
                                        addPhone( value, PhonesColumns.TYPE_HOME, preferred );
687
399
                        else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
688
 
                                type = PhonesColumns.TYPE_MOBILE;
 
400
                                addPhone( value, PhonesColumns.TYPE_MOBILE, preferred );
689
401
                        if( types.contains( "FAX" ) )
690
402
                                if( types.contains( "HOME" ) )
691
 
                                        type = PhonesColumns.TYPE_FAX_HOME;
 
403
                                        addPhone( value, PhonesColumns.TYPE_FAX_HOME, preferred );
692
404
                                else
693
 
                                        type = PhonesColumns.TYPE_FAX_WORK;
 
405
                                        addPhone( value, PhonesColumns.TYPE_FAX_WORK, preferred );
694
406
                        if( types.contains( "PAGER" ) )
695
 
                                type = PhonesColumns.TYPE_PAGER;
696
 
 
697
 
                        // add phone number
698
 
                        addPhone( value, type, preferred );
 
407
                                addPhone( value, PhonesColumns.TYPE_PAGER, preferred );
699
408
                }
700
409
 
701
410
                public void parseEMAIL( String[] params, String value )
702
 
                        throws ParseException
703
411
                {
704
412
                        if( value.length() == 0 ) return;
705
413
 
706
414
                        Set< String > types = extractTypes( params, Arrays.asList(
707
 
                                "PREF", "WORK", "HOME", "INTERNET" ) );
 
415
                                        "PREF", "WORK", "HOME", "INTERNET" ) );
708
416
 
709
417
                        // here's the logic...
710
418
                        boolean preferred = types.contains( "PREF" );
715
423
                }
716
424
 
717
425
                public void finaliseParsing()
718
 
                        throws ParseException, SkipContactException,
719
 
                        AbortImportException
 
426
                                throws ParseException, SkipContactException,
 
427
                                AbortImportException
720
428
                {
721
429
                        // missing version (and data is present)
722
 
                        if( _version == null && _buffers != null )
 
430
                        if( _version == null && _lines != null )
723
431
                                throw new ParseException( R.string.error_vcf_malformed );
724
432
 
725
433
                        //  missing name properties?
726
 
                        if( _name_level == NAMELEVEL_NONE )
 
434
                        if( _nameLevel == NAMELEVEL_NONE )
727
435
                                throw new ParseException( R.string.error_vcf_noname );
728
436
 
729
437
                        // check if we should import this one? If we've already got an 'N'-
730
438
                        // type name, this will already have been done by parseN() so we
731
439
                        // mustn't do this here (or it could prompt twice!)
732
 
                        if( _name_level < NAMELEVEL_N && !isImportRequired( getName() ) )
 
440
                        if( _nameLevel < NAMELEVEL_N && !isImportRequired( getName() ) )
733
441
                                throw new SkipContactException();
734
442
                }
735
443
 
 
444
                private String undoCharsetAndEncoding( String[] params, String value )
 
445
                                throws ParseException
 
446
                {
 
447
                        // check encoding/charset
 
448
                        String charset, encoding;
 
449
                        if( ( charset = checkParam( params, "CHARSET" ) ) != null &&
 
450
                                        !charset.equals( "UTF-8" ) && !charset.equals( "UTF-16" ) )
 
451
                                throw new ParseException( R.string.error_vcf_charset );
 
452
                        if( ( encoding = checkParam( params, "ENCODING" ) ) != null &&
 
453
                                        !encoding.equals( "QUOTED-PRINTABLE" ) )
 
454
                                throw new ParseException( R.string.error_vcf_encoding );
 
455
 
 
456
                        // do decoding?
 
457
                        if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
 
458
                                return unencodeQuotedPrintable( value, charset );
 
459
 
 
460
                        // nothing to do!
 
461
                        return value;
 
462
                }
 
463
 
736
464
                private String checkParam( String[] params, String name )
737
465
                {
738
 
                        Pattern p = Pattern.compile(
739
 
                                "^" + name + "[ \\t]*=[ \\t]*(\"?)(.*)\\1$" );
 
466
                        Pattern p = Pattern.compile( "^" + name + "[ \\t]*=[ \\t]*(.*)$" );
740
467
                        for( int i = 0; i < params.length; i++ ) {
741
468
                                Matcher m = p.matcher( params[ i ] );
742
469
                                if( m.matches() )
743
 
                                        return m.group( 2 );
 
470
                                        return m.group( 1 );
744
471
                        }
745
472
                        return null;
746
473
                }
747
474
 
748
475
                private Set< String > extractTypes( String[] params,
749
 
                        List< String > valid_types )
 
476
                                List< String > validTypes )
750
477
                {
751
478
                        HashSet< String > types = new HashSet< String >();
752
479
 
753
480
                        // get 3.0-style TYPE= param
754
 
                        String type_param;
755
 
                        if( ( type_param = checkParam( params, "TYPE" ) ) != null ) {
756
 
                                String[] parts = type_param.split( "," );
757
 
                                for( int i = 0; i < parts.length; i++ )
758
 
                                        if( valid_types.contains( parts[ i ] ) )
759
 
                                                types.add( parts[ i ] );
 
481
                        String typeParam;
 
482
                        if( ( typeParam = checkParam( params, "TYPE" ) ) != null ) {
 
483
                                String[] bits = typeParam.split( "," );
 
484
                                for( int i = 0; i < bits.length; i++ )
 
485
                                        if( validTypes.contains( bits[ i ] ) )
 
486
                                                types.add( bits[ i ] );
760
487
                        }
761
488
 
762
489
                        // get 2.1-style type param
763
490
                        if( _version.equals( "2.1" ) ) {
764
491
                                for( int i = 1; i < params.length; i++ )
765
 
                                        if( valid_types.contains( params[ i ] ) )
 
492
                                        if( validTypes.contains( params[ i ] ) )
766
493
                                                types.add( params[ i ] );
767
494
                        }
768
495
 
769
496
                        return types;
770
497
                }
771
498
 
772
 
                private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
 
499
                private String unencodeQuotedPrintable( String str, String charset )
773
500
                {
774
 
                        boolean another = false;
 
501
                        // default encoding scheme
 
502
                        if( charset == null ) charset = "UTF-8";
775
503
 
776
 
                        // unencode quoted-printable encoding, as per RFC1521 section 5.1
777
 
                        byte[] out = new byte[ in.limit() - in.position() ];
 
504
                        // unencode quoted-pritable encoding, as per RFC1521 section 5.1
 
505
                        byte[] bytes = new byte[ str.length() ];
778
506
                        int j = 0;
779
 
                        for( int i = in.position(); i < in.limit(); i++ )
780
 
                        {
781
 
                                // get next char and process...
782
 
                                byte ch = in.array()[ i ];
783
 
                                if( ch == '=' && i < in.limit() - 2 )
784
 
                                {
785
 
                                        // we found a =XX format byte, add it
786
 
                                        out[ j ] = (byte)(
787
 
                                                        Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
788
 
                                                        Character.digit( in.array()[ i + 2 ], 16 ) );
 
507
                        for( int i = 0; i < str.length(); i++, j++ ) {
 
508
                                char ch = str.charAt( i );
 
509
                                if( ch == '=' && i < str.length() - 2 ) {
 
510
                                        bytes[ j ] = (byte)(
 
511
                                                        Character.digit( str.charAt( i + 1 ), 16 ) * 16 +
 
512
                                                        Character.digit( str.charAt( i + 2 ), 16 ) );
789
513
                                        i += 2;
790
514
                                }
791
 
                                else if( ch == '=' && i == in.limit() - 1 )
792
 
                                {
793
 
                                        // we found a '=' at the end of a line signifying a multi-
794
 
                                        // line string, so we don't add it.
795
 
                                        another = true;
796
 
                                        continue;
797
 
                                }
798
515
                                else
799
 
                                        // just a normal char...
800
 
                                        out[ j ] = (byte)ch;
801
 
                                j++;
802
 
                        }
803
 
 
804
 
                        return new UnencodeResult( another, ByteBuffer.wrap( out, 0, j ) );
805
 
                }
806
 
 
807
 
                private ByteBuffer transcodeAsciiToUtf8( ByteBuffer in )
808
 
                {
809
 
                        // transcode
810
 
                        byte[] out = new byte[ ( in.limit() - in.position() ) * 2 ];
811
 
                        int j = 0;
812
 
                        for( int a = in.position(); a < in.limit(); a++ )
813
 
                        {
814
 
                                // if char is < 127, keep it as-is
815
 
                                if( in.array()[ a ] >= 0 )
816
 
                                        out[ j++ ] = in.array()[ a ];
817
 
 
818
 
                                // else, convert it to UTF-8
819
 
                                else {
820
 
                                        int b = 0xff & (int)in.array()[ a ];
821
 
                                        out[ j++ ] = (byte)( 0xc0 | ( b >> 6 ) );
822
 
                                        out[ j++ ] = (byte)( 0x80 | ( b & 0x3f ) );
823
 
                                }
824
 
                        }
825
 
 
826
 
                        return ByteBuffer.wrap( out, 0, j );
 
516
                                        bytes[ j ] = (byte)ch;
 
517
                        }
 
518
                        try {
 
519
                                return new String( bytes, 0, j, charset );
 
520
                        } catch( UnsupportedEncodingException e ) { }
 
521
                        return null;
827
522
                }
828
523
        }
829
524
}