/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts
6 by edam
- added GPL header comments to all files
1
/*
2
 * VCFImporter.java
3
 *
4
 * Copyright (C) 2009 Tim Marston <edam@waxworlds.org>
5
 *
6
 * This file is part of the Import Contacts program (hereafter referred
7
 * to as "this program"). For more information, see
8
 * http://www.waxworlds.org/edam/software/android/import-contacts
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
24
package org.waxworlds.edam.importcontacts;
1 by edam
Initial import
25
26
import java.io.BufferedReader;
27
import java.io.File;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
28
import java.io.FileInputStream;
1 by edam
Initial import
29
import java.io.FileNotFoundException;
30
import java.io.FileReader;
31
import java.io.FilenameFilter;
32
import java.io.IOException;
33
import java.io.UnsupportedEncodingException;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
34
import java.nio.ByteBuffer;
1 by edam
Initial import
35
import java.util.Arrays;
36
import java.util.HashSet;
37
import java.util.List;
38
import java.util.Set;
39
import java.util.Vector;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42
43
import android.content.SharedPreferences;
44
import android.provider.Contacts;
45
import android.provider.Contacts.PhonesColumns;
46
47
public class VCFImporter extends Importer
48
{
49
	private int _vCardCount = 0;
50
	private int _progress = 0;
51
52
	public VCFImporter( Doit doit )
53
	{
54
		super( doit );
55
	}
56
57
	@Override
58
	protected void onImport() throws AbortImportException
59
	{
60
		SharedPreferences prefs = getSharedPreferences();
61
62
		// update UI
63
		setProgressMessage( R.string.doit_scanning );
64
65
		// get a list of vcf files
66
		File[] files = null;
67
		try
68
		{
69
			// open directory
19 by edam
- added file chooser
70
			String path = "/sdcard" + prefs.getString( "location", "/" );
71
			File file = new File( path );
72
			if( !file.exists() )
1 by edam
Initial import
73
				showError( R.string.error_locationnotfound );
74
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
75
			// directory, or file?
19 by edam
- added file chooser
76
			if( file.isDirectory() )
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
77
			{
78
				// get files
79
				class VCardFilter implements FilenameFilter {
80
					public boolean accept( File dir, String name ) {
81
						return name.toLowerCase().endsWith( ".vcf" );
82
					}
13 by edam
- converted project to use Android 1.5 SDK
83
				}
19 by edam
- added file chooser
84
				files = file.listFiles( new VCardFilter() );
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
85
			}
86
			else
87
			{
88
				// use just this file
89
				files = new File[ 1 ];
19 by edam
- added file chooser
90
				files[ 0 ] = file;
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
91
			}
1 by edam
Initial import
92
		}
93
		catch( SecurityException e ) {
94
			showError( R.string.error_locationpermissions );
95
		}
96
97
		// check num files and set progress max
98
		if( files != null && files.length > 0 )
99
			setProgressMax( files.length );
100
		else
101
			showError( R.string.error_locationnofiles );
102
103
		// scan through the files
104
		setTmpProgress( 0 );
105
		for( int i = 0; i < files.length; i++ ) {
106
			countVCardFile( files[ i ] );
107
			setTmpProgress( i );
108
		}
109
		setProgressMax( _vCardCount );	// will also update tmp progress
110
111
		// import them
112
		setProgress( 0 );
113
		for( int i = 0; i < files.length; i++ )
114
			importVCardFile( files[ i ] );
115
	}
116
117
	private void countVCardFile( File file ) throws AbortImportException
118
	{
119
		try
120
		{
121
			// open file
122
			BufferedReader reader = new BufferedReader(
123
					new FileReader( file ) );
124
125
			// read
126
			String line;
127
			boolean inVCard = false;
128
			while( ( line = reader.readLine() ) != null )
129
			{
130
				if( !inVCard ) {
131
					// look for vcard beginning
132
					if( line.matches( "^BEGIN[ \\t]*:[ \\t]*VCARD" ) ) {
133
						inVCard = true;
134
						_vCardCount++;
135
					}
136
				}
137
				else if( line.matches( "^END[ \\t]*:[ \\t]*VCARD" ) )
138
					inVCard = false;
139
			}
140
141
		}
142
		catch( FileNotFoundException e ) {
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
143
			showError( getText( R.string.error_filenotfound ) +
144
				file.getName() );
1 by edam
Initial import
145
		}
146
		catch( IOException e ) {
147
			showError( getText( R.string.error_ioerror ) + file.getName() );
148
		}
149
	}
150
151
	private void importVCardFile( File file ) throws AbortImportException
152
	{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
1 by edam
Initial import
161
		try
162
		{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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() );
1 by edam
Initial import
170
		}
171
		catch( FileNotFoundException e ) {
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
172
			showError( getText( R.string.error_filenotfound ) +
173
				file.getName() );
1 by edam
Initial import
174
		}
175
		catch( IOException e ) {
176
			showError( getText( R.string.error_ioerror ) + file.getName() );
177
		}
178
	}
179
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
180
	private void importVCardFileContent( byte[] content, String fileName )
1 by edam
Initial import
181
			throws AbortImportException
182
	{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
183
		ByteBuffer buffers[] = getLinesFromContent( content );
184
185
		// go through lines
1 by edam
Initial import
186
		VCard vCard = null;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
187
		for( int i = 0; i < buffers.length; i++ )
1 by edam
Initial import
188
		{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
			}
1 by edam
Initial import
199
200
			if( vCard == null ) {
201
				// look for vcard beginning
202
				if( line.matches( "^BEGIN[ \\t]*:[ \\t]*VCARD" ) ) {
203
					setProgress( ++_progress );
204
					vCard = new VCard();
205
				}
206
			}
207
			else {
208
				// look for vcard content or ending
209
				if( line.matches( "^END[ \\t]*:[ \\t]*VCARD" ) )
210
				{
211
					// store vcard and do away with it
212
					try {
213
						vCard.finaliseParsing();
214
						importContact( vCard );
215
					}
216
					catch( VCard.ParseException e ) {
217
						skipContact();
218
						if( !showContinue(
219
								getText( R.string.error_vcf_parse ).toString()
220
								+ fileName + "\n" + e.getMessage() ) )
3 by edam
- added "all done" message
221
							finish( ACTION_ABORT );
1 by edam
Initial import
222
					}
223
					catch( VCard.SkipContactException e ) {
224
						skipContact();
225
						// do nothing
226
					}
227
					vCard = null;
228
				}
229
				else
230
				{
231
					// try giving the line to the vcard
232
					try {
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
233
						vCard.parseLine( buffers[ i ] );
1 by edam
Initial import
234
					}
235
					catch( VCard.ParseException e ) {
236
						skipContact();
237
						if( !showContinue(
238
								getText( R.string.error_vcf_parse ).toString()
239
								+ fileName + "\n" + e.getMessage() ) )
3 by edam
- added "all done" message
240
							finish( ACTION_ABORT );
1 by edam
Initial import
241
242
						// although we're continuing, we still need to abort
243
						// this vCard. Further lines will be ignored until we
244
						// get to another BEGIN:VCARD line.
245
						vCard = null;
246
					}
247
					catch( VCard.SkipContactException e ) {
248
						skipContact();
249
						// abort this vCard. Further lines will be ignored until
250
						// we get to another BEGIN:VCARD line.
251
						vCard = null;
252
					}
253
				}
254
			}
255
		}
256
	}
257
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
1 by edam
Initial import
282
	private class VCard extends ContactData
283
	{
284
		private final static int NAMELEVEL_NONE = 0;
285
		private final static int NAMELEVEL_ORG = 1;
286
		private final static int NAMELEVEL_FN = 2;
287
		private final static int NAMELEVEL_N = 3;
288
289
		private String _version = null;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
290
		private Vector< ByteBuffer > _buffers = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
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;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
299
			private ByteBuffer _buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
300
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
301
			public UnencodeResult( boolean another_line_required,
302
				ByteBuffer buffer )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
303
			{
304
				_another_line_required = another_line_required;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
305
				_buffer = buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
306
			}
307
308
			public boolean isAnotherLineRequired()
309
			{
310
				return _another_line_required;
311
			}
312
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
313
			public ByteBuffer getBuffer()
314
			{
315
				return _buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
316
			}
317
		}
1 by edam
Initial import
318
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
319
		@SuppressWarnings("serial")
1 by edam
Initial import
320
		protected class ParseException extends Exception
321
		{
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
322
			@SuppressWarnings("unused")
1 by edam
Initial import
323
			public ParseException( String error )
324
			{
325
				super( error );
326
			}
327
328
			public ParseException( int res )
329
			{
330
				super( VCFImporter.this.getText( res ).toString() );
331
			}
332
		}
333
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
334
		@SuppressWarnings("serial")
1 by edam
Initial import
335
		protected class SkipContactException extends Exception { }
336
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
337
		public void parseLine( ByteBuffer buffer )
1 by edam
Initial import
338
				throws ParseException, SkipContactException,
339
				AbortImportException
340
		{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
352
			// ignore empty lines
33 by edam
- fixed a couple of java string comparison checks
353
			if( line.trim().equals( "" ) ) return;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
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
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
			{
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
365
				String[] parts = line.split( ":", 2 );
366
				if( parts.length == 2 ) {
367
					name_and_params = parts[ 0 ].trim();
368
					string_value = parts[ 1 ].trim();
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
			}
1 by edam
Initial import
380
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
381
			// if we haven't yet got a version, we won't be paring anything!
1 by edam
Initial import
382
			if( _version == null )
383
			{
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
384
				// is this a version?
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
385
				if( name_and_params.equals( "VERSION" ) )
1 by edam
Initial import
386
				{
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
387
					// yes, check/store it
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
388
					if( !string_value.equals( "2.1" ) &&
389
							!string_value.equals( "3.0" ) )
1 by edam
Initial import
390
						throw new ParseException( R.string.error_vcf_version );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
391
					_version = string_value;
1 by edam
Initial import
392
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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;
1 by edam
Initial import
398
				}
399
				else
400
				{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
401
					// no, so stash this buffer till we have a version
402
					if( _buffers == null )
403
						_buffers = new Vector< ByteBuffer >();
404
					_buffers.add( buffer );
1 by edam
Initial import
405
				}
406
			}
407
			else
408
			{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
409
				// value bytes, for processing
410
				ByteBuffer value;
411
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
412
				if( _parser_in_multiline )
413
				{
414
					// if we're currently in a multi-line value, use the stored
415
					// property name and parameters
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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 );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
431
				}
432
				else
433
				{
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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 );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
441
442
					// reset the saved multi-line state
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
443
					_parser_current_name_and_params = name_and_params;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
444
					_parser_buffered_value_so_far = "";
445
				}
446
1 by edam
Initial import
447
				// get parameter parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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();
1 by edam
Initial import
451
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
452
				// parse encoding parameter
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
453
				String encoding = checkParam( name_param_parts, "ENCODING" );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
454
				if( encoding != null ) encoding = encoding.toUpperCase();
455
				if( encoding != null && !encoding.equals( "8BIT" ) &&
456
					!encoding.equals( "QUOTED-PRINTABLE" ) )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
457
					//&& !encoding.equals( "BASE64" ) )
458
				{
459
					throw new ParseException( R.string.error_vcf_encoding );
460
				}
461
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
462
				// parse charset parameter
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
463
				String charset = checkParam( name_param_parts, "CHARSET" );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
471
				// do unencoding (or default to a fake unencoding result with
472
				// the raw string)
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
473
				UnencodeResult unencoding_result = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
474
				if( encoding != null && encoding.equals( "QUOTED-PRINTABLE" ) )
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
475
					unencoding_result = unencodeQuotedPrintable( value );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
476
//				else if( encoding != null && encoding.equals( "BASE64" ) )
34 by edam
- check for empty data "values" after parsing line parameters, so that we catch parameter errors (such as unknown encoding types).
477
//					unencoding_result = unencodeBase64( props[ 1 ], charset );
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
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
33 by edam
- fixed a couple of java string comparison checks
485
				if( charset == null || charset.equals( "ASCII" ) ) {
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
486
					value = transcodeAsciiToUtf8( value );
487
					charset = "UTF-8";
488
				}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
489
490
				// process charset
491
				try {
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
492
					string_value =
493
						new String( value.array(), value.position(),
494
							value.limit() - value.position(), charset );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
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 ) {
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
501
					_parser_buffered_value_so_far += string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
502
					return;
503
				}
504
505
				// add on buffered multi-line content
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
506
				String complete_value =
507
					_parser_buffered_value_so_far + string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
508
34 by edam
- check for empty data "values" after parsing line parameters, so that we catch parameter errors (such as unknown encoding types).
509
				// ignore empty values
510
				if( complete_value.length() < 1 ) return;
511
1 by edam
Initial import
512
				// parse some properties
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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 );
1 by edam
Initial import
523
			}
524
		}
525
526
		private void parseN( String[] params, String value )
527
				throws ParseException, SkipContactException,
528
				AbortImportException
529
		{
530
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
531
			if( _name_level >= NAMELEVEL_N ) return;
1 by edam
Initial import
532
533
			// get name parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
534
			String[] name_parts = value.split( ";" );
535
			for( int i = 0; i < name_parts.length; i++ )
536
				name_parts[ i ] = name_parts[ i ].trim();
1 by edam
Initial import
537
538
			// build name
539
			value = "";
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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 ];
1 by edam
Initial import
544
545
			// set name
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
546
			setName( value );
547
			_name_level = NAMELEVEL_N;
1 by edam
Initial import
548
549
			// check now to see if we need to import this contact (to avoid
550
			// parsing the rest of the vCard unnecessarily)
551
			if( !isImportRequired( getName() ) )
552
				throw new SkipContactException();
553
		}
554
555
		private void parseFN( String[] params, String value )
556
				throws ParseException, SkipContactException
557
		{
558
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
559
			if( _name_level >= NAMELEVEL_FN ) return;
1 by edam
Initial import
560
561
			// set name
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
562
			setName( value );
563
			_name_level = NAMELEVEL_FN;
1 by edam
Initial import
564
		}
565
566
		private void parseORG( String[] params, String value )
567
				throws ParseException, SkipContactException
568
		{
569
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
570
			if( _name_level >= NAMELEVEL_ORG ) return;
1 by edam
Initial import
571
572
			// get org parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
573
			String[] org_parts = value.split( ";" );
574
			for( int i = 0; i < org_parts.length; i++ )
575
				org_parts[ i ] = org_parts[ i ].trim();
1 by edam
Initial import
576
577
			// build name
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
578
			if( org_parts.length > 1 && org_parts[ 0 ].length() == 0 )
579
				value = org_parts[ 1 ];
1 by edam
Initial import
580
			else
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
581
				value = org_parts[ 0 ];
1 by edam
Initial import
582
583
			// set name
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
584
			setName( value );
585
			_name_level = NAMELEVEL_ORG;
1 by edam
Initial import
586
		}
587
588
		private void parseTEL( String[] params, String value )
589
				throws ParseException
590
		{
591
			if( value.length() == 0 ) return;
592
593
			Set< String > types = extractTypes( params, Arrays.asList(
594
					"PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
595
					"PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
596
597
			// here's the logic...
598
			boolean preferred = types.contains( "PREF" );
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
599
			int type = PhonesColumns.TYPE_MOBILE;
1 by edam
Initial import
600
			if( types.contains( "VOICE" ) )
601
				if( types.contains( "WORK" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
602
					type = PhonesColumns.TYPE_WORK;
1 by edam
Initial import
603
				else
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
604
					type = PhonesColumns.TYPE_HOME;
1 by edam
Initial import
605
			else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
606
				type = PhonesColumns.TYPE_MOBILE;
1 by edam
Initial import
607
			if( types.contains( "FAX" ) )
608
				if( types.contains( "HOME" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
609
					type = PhonesColumns.TYPE_FAX_HOME;
1 by edam
Initial import
610
				else
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
611
					type = PhonesColumns.TYPE_FAX_WORK;
1 by edam
Initial import
612
			if( types.contains( "PAGER" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
613
				type = PhonesColumns.TYPE_PAGER;
614
615
			// add phone number
616
			addPhone( value, type, preferred );
1 by edam
Initial import
617
		}
618
619
		public void parseEMAIL( String[] params, String value )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
620
				throws ParseException
1 by edam
Initial import
621
		{
622
			if( value.length() == 0 ) return;
623
624
			Set< String > types = extractTypes( params, Arrays.asList(
625
					"PREF", "WORK", "HOME", "INTERNET" ) );
626
627
			// here's the logic...
628
			boolean preferred = types.contains( "PREF" );
629
			if( types.contains( "WORK" ) )
630
				addEmail( value, Contacts.ContactMethods.TYPE_WORK, preferred );
631
			else
632
				addEmail( value, Contacts.ContactMethods.TYPE_HOME, preferred );
633
		}
634
635
		public void finaliseParsing()
636
				throws ParseException, SkipContactException,
637
				AbortImportException
638
		{
639
			// missing version (and data is present)
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
640
			if( _version == null && _buffers != null )
1 by edam
Initial import
641
				throw new ParseException( R.string.error_vcf_malformed );
642
643
			//  missing name properties?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
644
			if( _name_level == NAMELEVEL_NONE )
1 by edam
Initial import
645
				throw new ParseException( R.string.error_vcf_noname );
646
647
			// check if we should import this one? If we've already got an 'N'-
648
			// type name, this will already have been done by parseN() so we
649
			// mustn't do this here (or it could prompt twice!)
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
650
			if( _name_level < NAMELEVEL_N && !isImportRequired( getName() ) )
1 by edam
Initial import
651
				throw new SkipContactException();
652
		}
653
654
		private String checkParam( String[] params, String name )
655
		{
656
			Pattern p = Pattern.compile( "^" + name + "[ \\t]*=[ \\t]*(.*)$" );
657
			for( int i = 0; i < params.length; i++ ) {
658
				Matcher m = p.matcher( params[ i ] );
659
				if( m.matches() )
660
					return m.group( 1 );
661
			}
662
			return null;
663
		}
664
665
		private Set< String > extractTypes( String[] params,
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
666
				List< String > valid_types )
1 by edam
Initial import
667
		{
668
			HashSet< String > types = new HashSet< String >();
669
670
			// get 3.0-style TYPE= param
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
671
			String type_param;
672
			if( ( type_param = checkParam( params, "TYPE" ) ) != null ) {
673
				String[] parts = type_param.split( "," );
674
				for( int i = 0; i < parts.length; i++ )
675
					if( valid_types.contains( parts[ i ] ) )
676
						types.add( parts[ i ] );
1 by edam
Initial import
677
			}
678
679
			// get 2.1-style type param
680
			if( _version.equals( "2.1" ) ) {
681
				for( int i = 1; i < params.length; i++ )
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
682
					if( valid_types.contains( params[ i ] ) )
1 by edam
Initial import
683
						types.add( params[ i ] );
684
			}
685
686
			return types;
687
		}
688
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
689
		private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
1 by edam
Initial import
690
		{
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
691
			boolean another = false;
692
1 by edam
Initial import
693
			// unencode quoted-pritable encoding, as per RFC1521 section 5.1
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
694
			byte[] out = new byte[ in.limit() - in.position() ];
1 by edam
Initial import
695
			int j = 0;
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
696
			for( int i = in.position(); i < in.limit(); i++ )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
697
			{
698
				// get next char and process...
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
699
				byte ch = in.array()[ i ];
700
				if( ch == '=' && i < in.limit() - 2 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
701
				{
702
					// we found a =XX format byte, add it
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
703
					out[ j ] = (byte)(
704
						Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
705
						Character.digit( in.array()[ i + 2 ], 16 ) );
1 by edam
Initial import
706
					i += 2;
707
				}
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
708
				else if( ch == '=' && i == in.limit() - 1 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
709
				{
710
					// we found a '=' at the end of a line signifying a multi-
711
					// line string, so we don't add it.
712
					another = true;
713
					continue;
714
				}
1 by edam
Initial import
715
				else
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
716
					// just a normal char...
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
717
					out[ j ] = (byte)ch;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
718
				j++;
1 by edam
Initial import
719
			}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
720
22 by edam
- read vCard files in as raw bytes now and convert to string only tentatively to check for version no.s and property names and params
721
			return new UnencodeResult( another, ByteBuffer.wrap( out, 0, j ) );
722
		}
723
724
		private ByteBuffer transcodeAsciiToUtf8( ByteBuffer in )
725
		{
726
			// transcode
727
			byte[] out = new byte[ ( in.limit() - in.position() ) * 2 ];
728
			int j = 0;
729
			for( int a = in.position(); a < in.limit(); a++ )
730
			{
731
				// if char is < 127, keep it as-is
732
				if( in.array()[ a ] >= 0 )
733
					out[ j++ ] = in.array()[ a ];
734
735
				// else, convert it to UTF-8
736
				else {
737
					int b = 0xff & (int)in.array()[ a ];
738
					out[ j++ ] = (byte)( 0xc0 | ( b >> 6 ) );
739
					out[ j++ ] = (byte)( 0x80 | ( b & 0x3f ) );
740
				}
741
			}
742
743
			return ByteBuffer.wrap( out, 0, j );
1 by edam
Initial import
744
		}
745
	}
746
}