/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
35
import java.util.List;
40
38
import java.util.regex.Matcher;
41
39
import java.util.regex.Pattern;
42
40
 
 
41
import org.waxworlds.importcontacts.Importer.AbortImportException;
 
42
 
43
43
import android.content.SharedPreferences;
44
44
import android.provider.Contacts;
45
45
import android.provider.Contacts.PhonesColumns;
67
67
                try
68
68
                {
69
69
                        // open directory
70
 
                        String path = "/sdcard" + prefs.getString( "location", "/" );
71
 
                        File file = new File( path );
72
 
                        if( !file.exists() )
 
70
                        String location = prefs.getString( "location", "" );
 
71
                        File dir = new File( location );
 
72
                        if( !dir.exists() || !dir.isDirectory() )
73
73
                                showError( R.string.error_locationnotfound );
74
74
 
75
 
                        // directory, or file?
76
 
                        if( file.isDirectory() )
77
 
                        {
78
 
                                // get files
79
 
                                class VCardFilter implements FilenameFilter {
80
 
                                        public boolean accept( File dir, String name ) {
81
 
                                                return name.toLowerCase().endsWith( ".vcf" );
82
 
                                        }
83
 
                                }
84
 
                                files = file.listFiles( new VCardFilter() );
85
 
                        }
86
 
                        else
87
 
                        {
88
 
                                // use just this file
89
 
                                files = new File[ 1 ];
90
 
                                files[ 0 ] = file;
91
 
                        }
 
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() );
92
82
                }
93
83
                catch( SecurityException e ) {
94
84
                        showError( R.string.error_locationpermissions );
140
130
 
141
131
                }
142
132
                catch( FileNotFoundException e ) {
143
 
                        showError( getText( R.string.error_filenotfound ) +
144
 
                                file.getName() );
 
133
                        showError( getText( R.string.error_filenotfound ) + file.getName() );
145
134
                }
146
135
                catch( IOException e ) {
147
136
                        showError( getText( R.string.error_ioerror ) + file.getName() );
150
139
 
151
140
        private void importVCardFile( File file ) throws AbortImportException
152
141
        {
153
 
                // check file is good
154
 
                if( !file.exists() )
155
 
                        showError( getText( R.string.error_filenotfound ) +
156
 
                                file.getName() );
157
 
                if( file.length() == 0 )
158
 
                        showError( getText( R.string.error_fileisempty ) +
159
 
                                file.getName() );
160
 
 
161
142
                try
162
143
                {
163
 
                        // open/read file
164
 
                        FileInputStream istream = new FileInputStream( file );
165
 
                        byte[] content = new byte[ (int)file.length() ];
166
 
                        istream.read( content );
167
 
 
168
 
                        // import
169
 
                        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() );
170
155
                }
171
156
                catch( FileNotFoundException e ) {
172
 
                        showError( getText( R.string.error_filenotfound ) +
173
 
                                file.getName() );
 
157
                        showError( getText( R.string.error_filenotfound ) + file.getName() );
174
158
                }
175
159
                catch( IOException e ) {
176
160
                        showError( getText( R.string.error_ioerror ) + file.getName() );
177
161
                }
178
162
        }
179
163
 
180
 
        private void importVCardFileContent( byte[] content, String fileName )
 
164
        private void importVCardFileContent( String content, String fileName )
181
165
                        throws AbortImportException
182
166
        {
183
 
                ByteBuffer buffers[] = getLinesFromContent( content );
 
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]", "" );
184
172
 
185
 
                // go through lines
 
173
                // get lines and parse them
 
174
                String[] lines = content.split( "\n" );
186
175
                VCard vCard = null;
187
 
                for( int i = 0; i < buffers.length; i++ )
 
176
                for( int i = 0; i < lines.length; i++ )
188
177
                {
189
 
                        // get a US-ASCII version of the line for processing
190
 
                        String line;
191
 
                        try {
192
 
                                line = new String( buffers[ i ].array(), buffers[ i ].position(),
193
 
                                        buffers[ i ].limit() - buffers[ i ].position(), "US-ASCII" );
194
 
                        }
195
 
                        catch( UnsupportedEncodingException e ) {
196
 
                                // we know US-ASCII is supported, so appease the compiler...
197
 
                                line = "";
198
 
                        }
 
178
                        String line = lines[ i ];
199
179
 
200
180
                        if( vCard == null ) {
201
181
                                // look for vcard beginning
230
210
                                {
231
211
                                        // try giving the line to the vcard
232
212
                                        try {
233
 
                                                vCard.parseLine( buffers[ i ] );
 
213
                                                vCard.parseLine( line );
234
214
                                        }
235
215
                                        catch( VCard.ParseException e ) {
236
216
                                                skipContact();
255
235
                }
256
236
        }
257
237
 
258
 
        private ByteBuffer[] getLinesFromContent( byte[] content )
259
 
        {
260
 
                // count lines in data
261
 
                int num_lines = 1;
262
 
                for( int a = 0; a < content.length; a++ )
263
 
                        if( content[ a ] == '\n' )
264
 
                                num_lines++;
265
 
 
266
 
                // get lines, removing \r's and \n's as we go
267
 
                ByteBuffer lines[] = new ByteBuffer[ num_lines ];
268
 
                int last = 0;
269
 
                for( int a = 0, b = 0; a < content.length; a++ )
270
 
                        if( content[ a ] == '\n' ) {
271
 
                                int to = ( a > 0 && content[ a - 1 ] == '\r' &&
272
 
                                        a - 1 >= last )? a - 1 : a;
273
 
                                lines[ b++ ] = ByteBuffer.wrap( content, last, to - last );
274
 
                                last = a + 1;
275
 
                        }
276
 
                lines[ lines.length - 1 ] = ByteBuffer.wrap( content, last,
277
 
                        content.length - last );
278
 
 
279
 
                return lines;
280
 
        }
281
 
 
282
238
        private class VCard extends ContactData
283
239
        {
284
240
                private final static int NAMELEVEL_NONE = 0;
287
243
                private final static int NAMELEVEL_N = 3;
288
244
 
289
245
                private String _version = null;
290
 
                private Vector< ByteBuffer > _buffers = null;
291
 
                private int _name_level = NAMELEVEL_NONE;
292
 
                private boolean _parser_in_multiline = false;
293
 
                private String _parser_current_name_and_params = null;
294
 
                private String _parser_buffered_value_so_far = "";
295
 
 
296
 
                protected class UnencodeResult
297
 
                {
298
 
                        private boolean _another_line_required;
299
 
                        private ByteBuffer _buffer;
300
 
 
301
 
                        public UnencodeResult( boolean another_line_required,
302
 
                                ByteBuffer buffer )
303
 
                        {
304
 
                                _another_line_required = another_line_required;
305
 
                                _buffer = buffer;
306
 
                        }
307
 
 
308
 
                        public boolean isAnotherLineRequired()
309
 
                        {
310
 
                                return _another_line_required;
311
 
                        }
312
 
 
313
 
                        public ByteBuffer getBuffer()
314
 
                        {
315
 
                                return _buffer;
316
 
                        }
317
 
                }
318
 
 
319
 
                @SuppressWarnings("serial")
 
246
                private Vector< String > _lines = null;
 
247
                private int _nameLevel = NAMELEVEL_NONE;
 
248
 
320
249
                protected class ParseException extends Exception
321
250
                {
322
 
                        @SuppressWarnings("unused")
323
251
                        public ParseException( String error )
324
252
                        {
325
253
                                super( error );
331
259
                        }
332
260
                }
333
261
 
334
 
                @SuppressWarnings("serial")
335
262
                protected class SkipContactException extends Exception { }
336
263
 
337
 
                public void parseLine( ByteBuffer buffer )
 
264
                public void parseLine( String line )
338
265
                                throws ParseException, SkipContactException,
339
266
                                AbortImportException
340
267
                {
341
 
                        // get a US-ASCII version of the line for processing
342
 
                        String line;
343
 
                        try {
344
 
                                line = new String( buffer.array(), buffer.position(),
345
 
                                        buffer.limit() - buffer.position(), "US-ASCII" );
346
 
                        }
347
 
                        catch( UnsupportedEncodingException e ) {
348
 
                                // we know US-ASCII is supported, so appease the compiler...
349
 
                                line = "";
350
 
                        }
351
 
 
352
 
                        // ignore empty lines
353
 
                        if( line.trim().equals( "" ) ) return;
354
 
 
355
 
                        // split line into name and value parts (this may turn out to be
356
 
                        // unwanted if the line is a subsequent line in a multi-line
357
 
                        // value, but we have to do this now to check for and handle VCF
358
 
                        // versions first). Also, the value part is only created tentatively
359
 
                        // because it may have an encoding/charset. Since we're treating it
360
 
                        // as UTF-8 (which is compatible with 7-bit US-ASCII) this is ok
361
 
                        // though so long as we later use the raw bytes. ALso we check for
362
 
                        // malformed property:name pairs.
363
 
                        String name_and_params, string_value;
364
 
                        {
365
 
                                String[] parts = line.split( ":", 2 );
366
 
                                if( parts.length == 2 ) {
367
 
                                        name_and_params = parts[ 0 ].trim();
368
 
                                        string_value = parts[ 1 ].trim();
369
 
                                        if( name_and_params.length() == 0 )
370
 
                                                throw new ParseException( R.string.error_vcf_malformed );
371
 
                                }
372
 
                                else
373
 
                                {
374
 
                                        if( !_parser_in_multiline )
375
 
                                                throw new ParseException( R.string.error_vcf_malformed );
376
 
                                        name_and_params = null;
377
 
                                        string_value = null;
378
 
                                }
379
 
                        }
380
 
 
381
 
                        // if we haven't yet got a version, we won't be paring anything!
 
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
 
382
276
                        if( _version == null )
383
277
                        {
384
 
                                // is this a version?
385
 
                                if( name_and_params.equals( "VERSION" ) )
 
278
                                if( props[ 0 ].equals( "VERSION" ) )
386
279
                                {
387
 
                                        // yes, check/store it
388
 
                                        if( !string_value.equals( "2.1" ) &&
389
 
                                                        !string_value.equals( "3.0" ) )
 
280
                                        // get version
 
281
                                        if( !props[ 1 ].equals( "2.1" ) &&
 
282
                                                        !props[ 1 ].equals( "3.0" ) )
390
283
                                                throw new ParseException( R.string.error_vcf_version );
391
 
                                        _version = string_value;
 
284
                                        _version = props[ 1 ];
392
285
 
393
 
                                        // parse any other buffers we've accumulated so far
394
 
                                        if( _buffers != null )
395
 
                                                for( int i = 0; i < _buffers.size(); i++ )
396
 
                                                        parseLine( _buffers.get( i ) );
397
 
                                        _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;
398
291
                                }
399
292
                                else
400
293
                                {
401
 
                                        // no, so stash this buffer till we have a version
402
 
                                        if( _buffers == null )
403
 
                                                _buffers = new Vector< ByteBuffer >();
404
 
                                        _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 );
405
298
                                }
406
299
                        }
407
300
                        else
408
301
                        {
409
 
                                // value bytes, for processing
410
 
                                ByteBuffer value;
411
 
 
412
 
                                if( _parser_in_multiline )
413
 
                                {
414
 
                                        // if we're currently in a multi-line value, use the stored
415
 
                                        // property name and parameters
416
 
                                        name_and_params = _parser_current_name_and_params;
417
 
 
418
 
                                        // find start of string (skip spaces/tabs)
419
 
                                        int pos = buffer.position();
420
 
                                        byte[] buffer_array = buffer.array();
421
 
                                        while( pos < buffer.limit() && (
422
 
                                                buffer_array[ pos ] == ' ' ||
423
 
                                                buffer_array[ pos ] == '\t' ) )
424
 
                                        {
425
 
                                                pos++;
426
 
                                        }
427
 
 
428
 
                                        // get value from buffer
429
 
                                        value = ByteBuffer.wrap( buffer.array(), pos,
430
 
                                                buffer.limit() - pos );
431
 
                                }
432
 
                                else
433
 
                                {
434
 
                                        // calculate how many chars to skip from beginning of line
435
 
                                        // so we skip the property "name:" part
436
 
                                        int pos = buffer.position() + name_and_params.length() + 1;
437
 
 
438
 
                                        // get value from buffer
439
 
                                        value = ByteBuffer.wrap( buffer.array(), pos,
440
 
                                                buffer.limit() - pos );
441
 
 
442
 
                                        // reset the saved multi-line state
443
 
                                        _parser_current_name_and_params = name_and_params;
444
 
                                        _parser_buffered_value_so_far = "";
445
 
                                }
446
 
 
447
302
                                // get parameter parts
448
 
                                String[] name_param_parts = name_and_params.split( ";", -1 );
449
 
                                for( int i = 0; i < name_param_parts.length; i++ )
450
 
                                        name_param_parts[ i ] = name_param_parts[ i ].trim();
451
 
 
452
 
                                // parse encoding parameter
453
 
                                String encoding = checkParam( name_param_parts, "ENCODING" );
454
 
                                if( encoding != null ) encoding = encoding.toUpperCase();
455
 
                                if( encoding != null && !encoding.equals( "8BIT" ) &&
456
 
                                        !encoding.equals( "QUOTED-PRINTABLE" ) )
457
 
                                        //&& !encoding.equals( "BASE64" ) )
458
 
                                {
459
 
                                        throw new ParseException( R.string.error_vcf_encoding );
460
 
                                }
461
 
 
462
 
                                // parse charset parameter
463
 
                                String charset = checkParam( name_param_parts, "CHARSET" );
464
 
                                if( charset != null ) charset = charset.toUpperCase();
465
 
                                if( charset != null && !charset.equals( "US-ASCII" ) &&
466
 
                                        !charset.equals( "ASCII" ) && !charset.equals( "UTF-8" ) )
467
 
                                {
468
 
                                        throw new ParseException( R.string.error_vcf_charset );
469
 
                                }
470
 
 
471
 
                                // do unencoding (or default to a fake unencoding result with
472
 
                                // the raw string)
473
 
                                UnencodeResult unencoding_result = null;
474
 
                                if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
475
 
                                        unencoding_result = unencodeQuotedPrintable( value );
476
 
//                              else if( encoding != null && encoding.equals( "BASE64" ) )
477
 
//                                      unencoding_result = unencodeBase64( props[ 1 ], charset );
478
 
                                if( unencoding_result != null ) {
479
 
                                        value = unencoding_result.getBuffer();
480
 
                                        _parser_in_multiline =
481
 
                                                unencoding_result.isAnotherLineRequired();
482
 
                                }
483
 
 
484
 
                                // convert 8-bit ASCII charset to US-ASCII
485
 
                                if( charset == null || charset.equals( "ASCII" ) ) {
486
 
                                        value = transcodeAsciiToUtf8( value );
487
 
                                        charset = "UTF-8";
488
 
                                }
489
 
 
490
 
                                // process charset
491
 
                                try {
492
 
                                        string_value =
493
 
                                                new String( value.array(), value.position(),
494
 
                                                        value.limit() - value.position(), charset );
495
 
                                } catch( UnsupportedEncodingException e ) {
496
 
                                        throw new ParseException( R.string.error_vcf_charset );
497
 
                                }
498
 
 
499
 
                                // handle multi-line requests
500
 
                                if( _parser_in_multiline ) {
501
 
                                        _parser_buffered_value_so_far += string_value;
502
 
                                        return;
503
 
                                }
504
 
 
505
 
                                // add on buffered multi-line content
506
 
                                String complete_value =
507
 
                                        _parser_buffered_value_so_far + string_value;
508
 
 
509
 
                                // ignore empty values
510
 
                                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();
511
306
 
512
307
                                // parse some properties
513
 
                                if( name_param_parts[ 0 ].equals( "N" ) )
514
 
                                        parseN( name_param_parts, complete_value );
515
 
                                else if( name_param_parts[ 0 ].equals( "FN" ) )
516
 
                                        parseFN( name_param_parts, complete_value );
517
 
                                else if( name_param_parts[ 0 ].equals( "ORG" ) )
518
 
                                        parseORG( name_param_parts, complete_value );
519
 
                                else if( name_param_parts[ 0 ].equals( "TEL" ) )
520
 
                                        parseTEL( name_param_parts, complete_value );
521
 
                                else if( name_param_parts[ 0 ].equals( "EMAIL" ) )
522
 
                                        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 ] );
523
318
                        }
524
319
                }
525
320
 
528
323
                                AbortImportException
529
324
                {
530
325
                        // already got a better name?
531
 
                        if( _name_level >= NAMELEVEL_N ) return;
 
326
                        if( _nameLevel >= NAMELEVEL_N ) return;
532
327
 
533
328
                        // get name parts
534
 
                        String[] name_parts = value.split( ";" );
535
 
                        for( int i = 0; i < name_parts.length; i++ )
536
 
                                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();
537
332
 
538
333
                        // build name
539
334
                        value = "";
540
 
                        if( name_parts.length > 1 && name_parts[ 1 ].length() > 0 )
541
 
                                value += name_parts[ 1 ];
542
 
                        if( name_parts.length > 0 && name_parts[ 0 ].length() > 0 )
543
 
                                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 ];
544
339
 
545
340
                        // set name
546
 
                        setName( value );
547
 
                        _name_level = NAMELEVEL_N;
 
341
                        setName( undoCharsetAndEncoding( params, value ) );
 
342
                        _nameLevel = NAMELEVEL_N;
548
343
 
549
344
                        // check now to see if we need to import this contact (to avoid
550
345
                        // parsing the rest of the vCard unnecessarily)
556
351
                                throws ParseException, SkipContactException
557
352
                {
558
353
                        // already got a better name?
559
 
                        if( _name_level >= NAMELEVEL_FN ) return;
 
354
                        if( _nameLevel >= NAMELEVEL_FN ) return;
560
355
 
561
356
                        // set name
562
 
                        setName( value );
563
 
                        _name_level = NAMELEVEL_FN;
 
357
                        setName( undoCharsetAndEncoding( params, value ) );
 
358
                        _nameLevel = NAMELEVEL_FN;
564
359
                }
565
360
 
566
361
                private void parseORG( String[] params, String value )
567
362
                                throws ParseException, SkipContactException
568
363
                {
569
364
                        // already got a better name?
570
 
                        if( _name_level >= NAMELEVEL_ORG ) return;
 
365
                        if( _nameLevel >= NAMELEVEL_ORG ) return;
571
366
 
572
367
                        // get org parts
573
 
                        String[] org_parts = value.split( ";" );
574
 
                        for( int i = 0; i < org_parts.length; i++ )
575
 
                                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();
576
371
 
577
372
                        // build name
578
 
                        if( org_parts.length > 1 && org_parts[ 0 ].length() == 0 )
579
 
                                value = org_parts[ 1 ];
 
373
                        if( orgparts[ 0 ].length() == 0 && orgparts.length > 1 )
 
374
                                value = orgparts[ 1 ];
580
375
                        else
581
 
                                value = org_parts[ 0 ];
 
376
                                value = orgparts[ 0 ];
582
377
 
583
378
                        // set name
584
 
                        setName( value );
585
 
                        _name_level = NAMELEVEL_ORG;
 
379
                        setName( undoCharsetAndEncoding( params, value ) );
 
380
                        _nameLevel = NAMELEVEL_ORG;
586
381
                }
587
382
 
588
383
                private void parseTEL( String[] params, String value )
596
391
 
597
392
                        // here's the logic...
598
393
                        boolean preferred = types.contains( "PREF" );
599
 
                        int type = PhonesColumns.TYPE_MOBILE;
600
394
                        if( types.contains( "VOICE" ) )
601
395
                                if( types.contains( "WORK" ) )
602
 
                                        type = PhonesColumns.TYPE_WORK;
 
396
                                        addPhone( value, PhonesColumns.TYPE_WORK, preferred );
603
397
                                else
604
 
                                        type = PhonesColumns.TYPE_HOME;
 
398
                                        addPhone( value, PhonesColumns.TYPE_HOME, preferred );
605
399
                        else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
606
 
                                type = PhonesColumns.TYPE_MOBILE;
 
400
                                addPhone( value, PhonesColumns.TYPE_MOBILE, preferred );
607
401
                        if( types.contains( "FAX" ) )
608
402
                                if( types.contains( "HOME" ) )
609
 
                                        type = PhonesColumns.TYPE_FAX_HOME;
 
403
                                        addPhone( value, PhonesColumns.TYPE_FAX_HOME, preferred );
610
404
                                else
611
 
                                        type = PhonesColumns.TYPE_FAX_WORK;
 
405
                                        addPhone( value, PhonesColumns.TYPE_FAX_WORK, preferred );
612
406
                        if( types.contains( "PAGER" ) )
613
 
                                type = PhonesColumns.TYPE_PAGER;
614
 
 
615
 
                        // add phone number
616
 
                        addPhone( value, type, preferred );
 
407
                                addPhone( value, PhonesColumns.TYPE_PAGER, preferred );
617
408
                }
618
409
 
619
410
                public void parseEMAIL( String[] params, String value )
620
 
                                throws ParseException
621
411
                {
622
412
                        if( value.length() == 0 ) return;
623
413
 
637
427
                                AbortImportException
638
428
                {
639
429
                        // missing version (and data is present)
640
 
                        if( _version == null && _buffers != null )
 
430
                        if( _version == null && _lines != null )
641
431
                                throw new ParseException( R.string.error_vcf_malformed );
642
432
 
643
433
                        //  missing name properties?
644
 
                        if( _name_level == NAMELEVEL_NONE )
 
434
                        if( _nameLevel == NAMELEVEL_NONE )
645
435
                                throw new ParseException( R.string.error_vcf_noname );
646
436
 
647
437
                        // check if we should import this one? If we've already got an 'N'-
648
438
                        // type name, this will already have been done by parseN() so we
649
439
                        // mustn't do this here (or it could prompt twice!)
650
 
                        if( _name_level < NAMELEVEL_N && !isImportRequired( getName() ) )
 
440
                        if( _nameLevel < NAMELEVEL_N && !isImportRequired( getName() ) )
651
441
                                throw new SkipContactException();
652
442
                }
653
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
 
654
464
                private String checkParam( String[] params, String name )
655
465
                {
656
 
                        Pattern p = Pattern.compile(
657
 
                                        "^" + name + "[ \\t]*=[ \\t]*(\"?)(.*)\\1$" );
 
466
                        Pattern p = Pattern.compile( "^" + name + "[ \\t]*=[ \\t]*(.*)$" );
658
467
                        for( int i = 0; i < params.length; i++ ) {
659
468
                                Matcher m = p.matcher( params[ i ] );
660
469
                                if( m.matches() )
661
 
                                        return m.group( 2 );
 
470
                                        return m.group( 1 );
662
471
                        }
663
472
                        return null;
664
473
                }
665
474
 
666
475
                private Set< String > extractTypes( String[] params,
667
 
                                List< String > valid_types )
 
476
                                List< String > validTypes )
668
477
                {
669
478
                        HashSet< String > types = new HashSet< String >();
670
479
 
671
480
                        // get 3.0-style TYPE= param
672
 
                        String type_param;
673
 
                        if( ( type_param = checkParam( params, "TYPE" ) ) != null ) {
674
 
                                String[] parts = type_param.split( "," );
675
 
                                for( int i = 0; i < parts.length; i++ )
676
 
                                        if( valid_types.contains( parts[ i ] ) )
677
 
                                                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 ] );
678
487
                        }
679
488
 
680
489
                        // get 2.1-style type param
681
490
                        if( _version.equals( "2.1" ) ) {
682
491
                                for( int i = 1; i < params.length; i++ )
683
 
                                        if( valid_types.contains( params[ i ] ) )
 
492
                                        if( validTypes.contains( params[ i ] ) )
684
493
                                                types.add( params[ i ] );
685
494
                        }
686
495
 
687
496
                        return types;
688
497
                }
689
498
 
690
 
                private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
 
499
                private String unencodeQuotedPrintable( String str, String charset )
691
500
                {
692
 
                        boolean another = false;
 
501
                        // default encoding scheme
 
502
                        if( charset == null ) charset = "UTF-8";
693
503
 
694
504
                        // unencode quoted-pritable encoding, as per RFC1521 section 5.1
695
 
                        byte[] out = new byte[ in.limit() - in.position() ];
 
505
                        byte[] bytes = new byte[ str.length() ];
696
506
                        int j = 0;
697
 
                        for( int i = in.position(); i < in.limit(); i++ )
698
 
                        {
699
 
                                // get next char and process...
700
 
                                byte ch = in.array()[ i ];
701
 
                                if( ch == '=' && i < in.limit() - 2 )
702
 
                                {
703
 
                                        // we found a =XX format byte, add it
704
 
                                        out[ j ] = (byte)(
705
 
                                                Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
706
 
                                                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 ) );
707
513
                                        i += 2;
708
514
                                }
709
 
                                else if( ch == '=' && i == in.limit() - 1 )
710
 
                                {
711
 
                                        // we found a '=' at the end of a line signifying a multi-
712
 
                                        // line string, so we don't add it.
713
 
                                        another = true;
714
 
                                        continue;
715
 
                                }
716
515
                                else
717
 
                                        // just a normal char...
718
 
                                        out[ j ] = (byte)ch;
719
 
                                j++;
720
 
                        }
721
 
 
722
 
                        return new UnencodeResult( another, ByteBuffer.wrap( out, 0, j ) );
723
 
                }
724
 
 
725
 
                private ByteBuffer transcodeAsciiToUtf8( ByteBuffer in )
726
 
                {
727
 
                        // transcode
728
 
                        byte[] out = new byte[ ( in.limit() - in.position() ) * 2 ];
729
 
                        int j = 0;
730
 
                        for( int a = in.position(); a < in.limit(); a++ )
731
 
                        {
732
 
                                // if char is < 127, keep it as-is
733
 
                                if( in.array()[ a ] >= 0 )
734
 
                                        out[ j++ ] = in.array()[ a ];
735
 
 
736
 
                                // else, convert it to UTF-8
737
 
                                else {
738
 
                                        int b = 0xff & (int)in.array()[ a ];
739
 
                                        out[ j++ ] = (byte)( 0xc0 | ( b >> 6 ) );
740
 
                                        out[ j++ ] = (byte)( 0x80 | ( b & 0x3f ) );
741
 
                                }
742
 
                        }
743
 
 
744
 
                        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;
745
522
                }
746
523
        }
747
524
}