/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;
36 by edam
- formatting: removed some double-indents on overrunning lines
37
import java.util.Iterator;
1 by edam
Initial import
38
import java.util.List;
39
import java.util.Set;
40
import java.util.Vector;
41
import java.util.regex.Matcher;
42
import java.util.regex.Pattern;
36 by edam
- formatting: removed some double-indents on overrunning lines
43
import java.util.NoSuchElementException;
44
import java.lang.UnsupportedOperationException;
1 by edam
Initial import
45
46
import android.content.SharedPreferences;
47
import android.provider.Contacts;
48
import android.provider.Contacts.PhonesColumns;
49
50
public class VCFImporter extends Importer
51
{
52
	private int _vCardCount = 0;
53
	private int _progress = 0;
54
55
	public VCFImporter( Doit doit )
56
	{
57
		super( doit );
58
	}
59
60
	@Override
61
	protected void onImport() throws AbortImportException
62
	{
63
		SharedPreferences prefs = getSharedPreferences();
64
65
		// update UI
66
		setProgressMessage( R.string.doit_scanning );
67
68
		// get a list of vcf files
69
		File[] files = null;
70
		try
71
		{
72
			// open directory
19 by edam
- added file chooser
73
			String path = "/sdcard" + prefs.getString( "location", "/" );
74
			File file = new File( path );
75
			if( !file.exists() )
1 by edam
Initial import
76
				showError( R.string.error_locationnotfound );
77
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
78
			// directory, or file?
19 by edam
- added file chooser
79
			if( file.isDirectory() )
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
80
			{
81
				// get files
82
				class VCardFilter implements FilenameFilter {
83
					public boolean accept( File dir, String name ) {
84
						return name.toLowerCase().endsWith( ".vcf" );
85
					}
13 by edam
- converted project to use Android 1.5 SDK
86
				}
19 by edam
- added file chooser
87
				files = file.listFiles( new VCardFilter() );
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
88
			}
89
			else
90
			{
91
				// use just this file
92
				files = new File[ 1 ];
19 by edam
- added file chooser
93
				files[ 0 ] = file;
15 by edam
- added facility to enter a filename (instead of a directory to scan) and just use that
94
			}
1 by edam
Initial import
95
		}
96
		catch( SecurityException e ) {
97
			showError( R.string.error_locationpermissions );
98
		}
99
100
		// check num files and set progress max
101
		if( files != null && files.length > 0 )
102
			setProgressMax( files.length );
103
		else
104
			showError( R.string.error_locationnofiles );
105
106
		// scan through the files
107
		setTmpProgress( 0 );
108
		for( int i = 0; i < files.length; i++ ) {
109
			countVCardFile( files[ i ] );
110
			setTmpProgress( i );
111
		}
112
		setProgressMax( _vCardCount );	// will also update tmp progress
113
114
		// import them
115
		setProgress( 0 );
116
		for( int i = 0; i < files.length; i++ )
117
			importVCardFile( files[ i ] );
118
	}
119
120
	private void countVCardFile( File file ) throws AbortImportException
121
	{
122
		try
123
		{
124
			// open file
125
			BufferedReader reader = new BufferedReader(
36 by edam
- formatting: removed some double-indents on overrunning lines
126
				new FileReader( file ) );
1 by edam
Initial import
127
128
			// read
129
			String line;
130
			boolean inVCard = false;
131
			while( ( line = reader.readLine() ) != null )
132
			{
133
				if( !inVCard ) {
134
					// look for vcard beginning
36 by edam
- formatting: removed some double-indents on overrunning lines
135
					if( line.matches( "^BEGIN:VCARD" ) ) {
1 by edam
Initial import
136
						inVCard = true;
137
						_vCardCount++;
138
					}
139
				}
36 by edam
- formatting: removed some double-indents on overrunning lines
140
				else if( line.matches( "^END:VCARD" ) )
1 by edam
Initial import
141
					inVCard = false;
142
			}
143
144
		}
145
		catch( FileNotFoundException e ) {
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
146
			showError( getText( R.string.error_filenotfound ) +
147
				file.getName() );
1 by edam
Initial import
148
		}
149
		catch( IOException e ) {
150
			showError( getText( R.string.error_ioerror ) + file.getName() );
151
		}
152
	}
153
154
	private void importVCardFile( File file ) throws AbortImportException
155
	{
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
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
1 by edam
Initial import
164
		try
165
		{
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
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() );
1 by edam
Initial import
173
		}
174
		catch( FileNotFoundException e ) {
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
175
			showError( getText( R.string.error_filenotfound ) +
176
				file.getName() );
1 by edam
Initial import
177
		}
178
		catch( IOException e ) {
179
			showError( getText( R.string.error_ioerror ) + file.getName() );
180
		}
181
	}
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
	private void importVCardFileContent( byte[] content, String fileName )
36 by edam
- formatting: removed some double-indents on overrunning lines
184
		throws AbortImportException
1 by edam
Initial import
185
	{
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
186
		// go through lines
1 by edam
Initial import
187
		VCard vCard = null;
36 by edam
- formatting: removed some double-indents on overrunning lines
188
		ContentLineIterator cli = new ContentLineIterator( content );
189
		while( cli.hasNext() )
1 by edam
Initial import
190
		{
36 by edam
- formatting: removed some double-indents on overrunning lines
191
			ByteBuffer buffer = cli.next();
192
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
193
			// get a US-ASCII version of the line for processing
194
			String line;
195
			try {
36 by edam
- formatting: removed some double-indents on overrunning lines
196
				line = new String( buffer.array(), buffer.position(),
197
					buffer.limit() - buffer.position(), "US-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
198
			}
199
			catch( UnsupportedEncodingException e ) {
200
				// we know US-ASCII is supported, so appease the compiler...
201
				line = "";
202
			}
1 by edam
Initial import
203
204
			if( vCard == null ) {
205
				// look for vcard beginning
36 by edam
- formatting: removed some double-indents on overrunning lines
206
				if( line.matches( "^BEGIN:VCARD" ) ) {
1 by edam
Initial import
207
					setProgress( ++_progress );
208
					vCard = new VCard();
209
				}
210
			}
211
			else {
212
				// look for vcard content or ending
36 by edam
- formatting: removed some double-indents on overrunning lines
213
				if( line.matches( "^END:VCARD" ) )
1 by edam
Initial import
214
				{
215
					// store vcard and do away with it
216
					try {
217
						vCard.finaliseParsing();
218
						importContact( vCard );
219
					}
220
					catch( VCard.ParseException e ) {
221
						skipContact();
222
						if( !showContinue(
36 by edam
- formatting: removed some double-indents on overrunning lines
223
							getText( R.string.error_vcf_parse ).toString()
224
							+ fileName + "\n" + e.getMessage() ) )
225
						{
3 by edam
- added "all done" message
226
							finish( ACTION_ABORT );
36 by edam
- formatting: removed some double-indents on overrunning lines
227
						}
1 by edam
Initial import
228
					}
229
					catch( VCard.SkipContactException e ) {
230
						skipContact();
231
						// do nothing
232
					}
233
					vCard = null;
234
				}
235
				else
236
				{
237
					// try giving the line to the vcard
238
					try {
36 by edam
- formatting: removed some double-indents on overrunning lines
239
						vCard.parseLine( buffer, line,
240
							cli.doesNextLineLookFolded() );
1 by edam
Initial import
241
					}
242
					catch( VCard.ParseException e ) {
243
						skipContact();
244
						if( !showContinue(
36 by edam
- formatting: removed some double-indents on overrunning lines
245
							getText( R.string.error_vcf_parse ).toString()
246
							+ fileName + "\n" + e.getMessage() ) )
247
						{
3 by edam
- added "all done" message
248
							finish( ACTION_ABORT );
36 by edam
- formatting: removed some double-indents on overrunning lines
249
						}
1 by edam
Initial import
250
251
						// although we're continuing, we still need to abort
252
						// this vCard. Further lines will be ignored until we
253
						// get to another BEGIN:VCARD line.
254
						vCard = null;
255
					}
256
					catch( VCard.SkipContactException e ) {
257
						skipContact();
258
						// abort this vCard. Further lines will be ignored until
259
						// we get to another BEGIN:VCARD line.
260
						vCard = null;
261
					}
262
				}
263
			}
264
		}
265
	}
266
36 by edam
- formatting: removed some double-indents on overrunning lines
267
	class ContentLineIterator implements Iterator< ByteBuffer >
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
268
	{
36 by edam
- formatting: removed some double-indents on overrunning lines
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 );
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
306
			}
36 by edam
- formatting: removed some double-indents on overrunning lines
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
		}
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
328
	}
329
1 by edam
Initial import
330
	private class VCard extends ContactData
331
	{
332
		private final static int NAMELEVEL_NONE = 0;
333
		private final static int NAMELEVEL_ORG = 1;
334
		private final static int NAMELEVEL_FN = 2;
335
		private final static int NAMELEVEL_N = 3;
336
337
		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
338
		private Vector< ByteBuffer > _buffers = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
339
		private int _name_level = NAMELEVEL_NONE;
36 by edam
- formatting: removed some double-indents on overrunning lines
340
		private boolean _parser_in_encoded_multiline = false;
341
		private boolean _parser_in_folded_multiline = false;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
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;
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
348
			private ByteBuffer _buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
349
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
350
			public UnencodeResult( boolean another_line_required,
351
				ByteBuffer buffer )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
352
			{
353
				_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
354
				_buffer = buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
355
			}
356
357
			public boolean isAnotherLineRequired()
358
			{
359
				return _another_line_required;
360
			}
361
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
362
			public ByteBuffer getBuffer()
363
			{
364
				return _buffer;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
365
			}
366
		}
1 by edam
Initial import
367
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
368
		@SuppressWarnings("serial")
1 by edam
Initial import
369
		protected class ParseException extends Exception
370
		{
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
371
			@SuppressWarnings("unused")
1 by edam
Initial import
372
			public ParseException( String error )
373
			{
374
				super( error );
375
			}
376
377
			public ParseException( int res )
378
			{
379
				super( VCFImporter.this.getText( res ).toString() );
380
			}
381
		}
382
14 by edam
- got rid of the pretend ImportContacts activity alltogether (and made the Intro activity the startup one)
383
		@SuppressWarnings("serial")
1 by edam
Initial import
384
		protected class SkipContactException extends Exception { }
385
36 by edam
- formatting: removed some double-indents on overrunning lines
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?
1 by edam
Initial import
430
			if( _version == null )
431
			{
36 by edam
- formatting: removed some double-indents on overrunning lines
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" ) )
1 by edam
Initial import
439
				{
36 by edam
- formatting: removed some double-indents on overrunning lines
440
					// yes, get it!
441
					String value = extractValueFromLine( buffer, line );
442
					if( !value.equals( "2.1" ) && !value.equals( "3.0" ) )
1 by edam
Initial import
443
						throw new ParseException( R.string.error_vcf_version );
36 by edam
- formatting: removed some double-indents on overrunning lines
444
					_version = value;
1 by edam
Initial import
445
36 by edam
- formatting: removed some double-indents on overrunning lines
446
					// parse any buffers we've been accumulating while we waited
447
					// for 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
448
					if( _buffers != null )
449
						for( int i = 0; i < _buffers.size(); i++ )
36 by edam
- formatting: removed some double-indents on overrunning lines
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() ) == ' ' );
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
455
					_buffers = null;
1 by edam
Initial import
456
				}
457
				else
458
				{
36 by edam
- formatting: removed some double-indents on overrunning lines
459
					// no, so stash this line till we get 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
460
					if( _buffers == null )
461
						_buffers = new Vector< ByteBuffer >();
462
					_buffers.add( buffer );
1 by edam
Initial import
463
				}
464
			}
465
			else
466
			{
36 by edam
- formatting: removed some double-indents on overrunning lines
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;
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
471
36 by edam
- formatting: removed some double-indents on overrunning lines
472
				if( _parser_in_encoded_multiline ||
473
					_parser_in_folded_multiline )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
474
				{
475
					// if we're currently in a multi-line value, use the stored
476
					// 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
477
					name_and_params = _parser_current_name_and_params;
478
36 by edam
- formatting: removed some double-indents on overrunning lines
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 )
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
484
						pos++;
485
36 by edam
- formatting: removed some double-indents on overrunning lines
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
						}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
495
				}
496
				else
497
				{
36 by edam
- formatting: removed some double-indents on overrunning lines
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
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
507
					// calculate how many chars to skip from beginning of line
508
					// so we skip the property "name:" part
36 by edam
- formatting: removed some double-indents on overrunning lines
509
					pos = buffer.position() + name_and_params.length() + 1;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
510
511
					// 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
512
					_parser_current_name_and_params = name_and_params;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
513
					_parser_buffered_value_so_far = "";
514
				}
515
36 by edam
- formatting: removed some double-indents on overrunning lines
516
				// get value from buffer, as raw bytes
517
				ByteBuffer value;
518
				value = ByteBuffer.wrap( buffer.array(), pos,
519
					buffer.limit() - pos );
520
1 by edam
Initial import
521
				// get parameter parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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();
1 by edam
Initial import
525
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
526
				// parse encoding parameter
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
527
				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
528
				if( encoding != null ) encoding = encoding.toUpperCase();
529
				if( encoding != null && !encoding.equals( "8BIT" ) &&
530
					!encoding.equals( "QUOTED-PRINTABLE" ) )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
531
					//&& !encoding.equals( "BASE64" ) )
532
				{
533
					throw new ParseException( R.string.error_vcf_encoding );
534
				}
535
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
536
				// parse charset parameter
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
537
				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
538
				if( charset != null ) charset = charset.toUpperCase();
539
				if( charset != null && !charset.equals( "US-ASCII" ) &&
36 by edam
- formatting: removed some double-indents on overrunning lines
540
					!charset.equals( "ASCII" ) &&
541
					!charset.equals( "UTF-8" ) )
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
542
				{
543
					throw new ParseException( R.string.error_vcf_charset );
544
				}
545
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
546
				// do unencoding (or default to a fake unencoding result with
547
				// 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
548
				UnencodeResult unencoding_result = null;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
549
				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
550
					unencoding_result = unencodeQuotedPrintable( value );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
551
//				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).
552
//					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
553
				if( unencoding_result != null ) {
554
					value = unencoding_result.getBuffer();
36 by edam
- formatting: removed some double-indents on overrunning lines
555
					_parser_in_encoded_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
556
						unencoding_result.isAnotherLineRequired();
557
				}
558
559
				// convert 8-bit ASCII charset to US-ASCII
33 by edam
- fixed a couple of java string comparison checks
560
				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
561
					value = transcodeAsciiToUtf8( value );
562
					charset = "UTF-8";
563
				}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
564
565
				// process charset
36 by edam
- formatting: removed some double-indents on overrunning lines
566
				String string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
567
				try {
36 by edam
- formatting: removed some double-indents on overrunning lines
568
					string_value = new String( value.array(), value.position(),
569
						value.limit() - value.position(), charset );
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
570
				} catch( UnsupportedEncodingException e ) {
571
					throw new ParseException( R.string.error_vcf_charset );
572
				}
573
36 by edam
- formatting: removed some double-indents on overrunning lines
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
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
579
				// handle multi-line requests
36 by edam
- formatting: removed some double-indents on overrunning lines
580
				if( _parser_in_encoded_multiline ||
581
					_parser_in_folded_multiline )
582
				{
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
583
					_parser_buffered_value_so_far += string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
584
					return;
585
				}
586
587
				// 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
588
				String complete_value =
589
					_parser_buffered_value_so_far + string_value;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
590
34 by edam
- check for empty data "values" after parsing line parameters, so that we catch parameter errors (such as unknown encoding types).
591
				// ignore empty values
592
				if( complete_value.length() < 1 ) return;
593
1 by edam
Initial import
594
				// parse some properties
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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 );
1 by edam
Initial import
605
			}
606
		}
607
608
		private void parseN( String[] params, String value )
36 by edam
- formatting: removed some double-indents on overrunning lines
609
			throws ParseException, SkipContactException,
610
			AbortImportException
1 by edam
Initial import
611
		{
612
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
613
			if( _name_level >= NAMELEVEL_N ) return;
1 by edam
Initial import
614
615
			// get name parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
616
			String[] name_parts = value.split( ";" );
617
			for( int i = 0; i < name_parts.length; i++ )
618
				name_parts[ i ] = name_parts[ i ].trim();
1 by edam
Initial import
619
620
			// build name
621
			value = "";
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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 ];
1 by edam
Initial import
626
627
			// set name
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
628
			setName( value );
629
			_name_level = NAMELEVEL_N;
1 by edam
Initial import
630
631
			// check now to see if we need to import this contact (to avoid
632
			// parsing the rest of the vCard unnecessarily)
633
			if( !isImportRequired( getName() ) )
634
				throw new SkipContactException();
635
		}
636
637
		private void parseFN( String[] params, String value )
36 by edam
- formatting: removed some double-indents on overrunning lines
638
			throws ParseException, SkipContactException
1 by edam
Initial import
639
		{
640
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
641
			if( _name_level >= NAMELEVEL_FN ) return;
1 by edam
Initial import
642
643
			// set name
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
644
			setName( value );
645
			_name_level = NAMELEVEL_FN;
1 by edam
Initial import
646
		}
647
648
		private void parseORG( String[] params, String value )
36 by edam
- formatting: removed some double-indents on overrunning lines
649
			throws ParseException, SkipContactException
1 by edam
Initial import
650
		{
651
			// already got a better name?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
652
			if( _name_level >= NAMELEVEL_ORG ) return;
1 by edam
Initial import
653
654
			// get org parts
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
655
			String[] org_parts = value.split( ";" );
656
			for( int i = 0; i < org_parts.length; i++ )
657
				org_parts[ i ] = org_parts[ i ].trim();
1 by edam
Initial import
658
659
			// build name
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
660
			if( org_parts.length > 1 && org_parts[ 0 ].length() == 0 )
661
				value = org_parts[ 1 ];
1 by edam
Initial import
662
			else
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
663
				value = org_parts[ 0 ];
1 by edam
Initial import
664
665
			// set name
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
666
			setName( value );
667
			_name_level = NAMELEVEL_ORG;
1 by edam
Initial import
668
		}
669
670
		private void parseTEL( String[] params, String value )
36 by edam
- formatting: removed some double-indents on overrunning lines
671
			throws ParseException
1 by edam
Initial import
672
		{
673
			if( value.length() == 0 ) return;
674
675
			Set< String > types = extractTypes( params, Arrays.asList(
36 by edam
- formatting: removed some double-indents on overrunning lines
676
				"PREF", "HOME", "WORK", "VOICE", "FAX", "MSG", "CELL",
677
				"PAGER", "BBS", "MODEM", "CAR", "ISDN", "VIDEO" ) );
1 by edam
Initial import
678
679
			// here's the logic...
680
			boolean preferred = types.contains( "PREF" );
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
681
			int type = PhonesColumns.TYPE_MOBILE;
1 by edam
Initial import
682
			if( types.contains( "VOICE" ) )
683
				if( types.contains( "WORK" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
684
					type = PhonesColumns.TYPE_WORK;
1 by edam
Initial import
685
				else
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
686
					type = PhonesColumns.TYPE_HOME;
1 by edam
Initial import
687
			else if( types.contains( "CELL" ) || types.contains( "VIDEO" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
688
				type = PhonesColumns.TYPE_MOBILE;
1 by edam
Initial import
689
			if( types.contains( "FAX" ) )
690
				if( types.contains( "HOME" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
691
					type = PhonesColumns.TYPE_FAX_HOME;
1 by edam
Initial import
692
				else
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
693
					type = PhonesColumns.TYPE_FAX_WORK;
1 by edam
Initial import
694
			if( types.contains( "PAGER" ) )
24 by edam
- import phone numbers even when they have no specified type (default to mobile)
695
				type = PhonesColumns.TYPE_PAGER;
696
697
			// add phone number
698
			addPhone( value, type, preferred );
1 by edam
Initial import
699
		}
700
701
		public void parseEMAIL( String[] params, String value )
36 by edam
- formatting: removed some double-indents on overrunning lines
702
			throws ParseException
1 by edam
Initial import
703
		{
704
			if( value.length() == 0 ) return;
705
706
			Set< String > types = extractTypes( params, Arrays.asList(
36 by edam
- formatting: removed some double-indents on overrunning lines
707
				"PREF", "WORK", "HOME", "INTERNET" ) );
1 by edam
Initial import
708
709
			// here's the logic...
710
			boolean preferred = types.contains( "PREF" );
711
			if( types.contains( "WORK" ) )
712
				addEmail( value, Contacts.ContactMethods.TYPE_WORK, preferred );
713
			else
714
				addEmail( value, Contacts.ContactMethods.TYPE_HOME, preferred );
715
		}
716
717
		public void finaliseParsing()
36 by edam
- formatting: removed some double-indents on overrunning lines
718
			throws ParseException, SkipContactException,
719
			AbortImportException
1 by edam
Initial import
720
		{
721
			// 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
722
			if( _version == null && _buffers != null )
1 by edam
Initial import
723
				throw new ParseException( R.string.error_vcf_malformed );
724
725
			//  missing name properties?
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
726
			if( _name_level == NAMELEVEL_NONE )
1 by edam
Initial import
727
				throw new ParseException( R.string.error_vcf_noname );
728
729
			// check if we should import this one? If we've already got an 'N'-
730
			// type name, this will already have been done by parseN() so we
731
			// mustn't do this here (or it could prompt twice!)
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
732
			if( _name_level < NAMELEVEL_N && !isImportRequired( getName() ) )
1 by edam
Initial import
733
				throw new SkipContactException();
734
		}
735
736
		private String checkParam( String[] params, String name )
737
		{
35 by edam
- accept parameters that are quoted (this doesn't appear to be part of the standards AFAICT, but Evolution apparently quotes parameter values)
738
			Pattern p = Pattern.compile(
36 by edam
- formatting: removed some double-indents on overrunning lines
739
				"^" + name + "[ \\t]*=[ \\t]*(\"?)(.*)\\1$" );
1 by edam
Initial import
740
			for( int i = 0; i < params.length; i++ ) {
741
				Matcher m = p.matcher( params[ i ] );
742
				if( m.matches() )
35 by edam
- accept parameters that are quoted (this doesn't appear to be part of the standards AFAICT, but Evolution apparently quotes parameter values)
743
					return m.group( 2 );
1 by edam
Initial import
744
			}
745
			return null;
746
		}
747
748
		private Set< String > extractTypes( String[] params,
36 by edam
- formatting: removed some double-indents on overrunning lines
749
			List< String > valid_types )
1 by edam
Initial import
750
		{
751
			HashSet< String > types = new HashSet< String >();
752
753
			// get 3.0-style TYPE= param
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
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 ] );
1 by edam
Initial import
760
			}
761
762
			// get 2.1-style type param
763
			if( _version.equals( "2.1" ) ) {
764
				for( int i = 1; i < params.length; i++ )
25 by edam
- fixed bug where parts[0] was assumed to exists after calling split()
765
					if( valid_types.contains( params[ i ] ) )
1 by edam
Initial import
766
						types.add( params[ i ] );
767
			}
768
769
			return types;
770
		}
771
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
772
		private UnencodeResult unencodeQuotedPrintable( ByteBuffer in )
1 by edam
Initial import
773
		{
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
774
			boolean another = false;
775
36 by edam
- formatting: removed some double-indents on overrunning lines
776
			// unencode quoted-printable 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
777
			byte[] out = new byte[ in.limit() - in.position() ];
1 by edam
Initial import
778
			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
779
			for( int i = in.position(); i < in.limit(); i++ )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
780
			{
781
				// 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
782
				byte ch = in.array()[ i ];
783
				if( ch == '=' && i < in.limit() - 2 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
784
				{
785
					// 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
786
					out[ j ] = (byte)(
36 by edam
- formatting: removed some double-indents on overrunning lines
787
							Character.digit( in.array()[ i + 1 ], 16 ) * 16 +
788
							Character.digit( in.array()[ i + 2 ], 16 ) );
1 by edam
Initial import
789
					i += 2;
790
				}
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
791
				else if( ch == '=' && i == in.limit() - 1 )
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
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
				}
1 by edam
Initial import
798
				else
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
799
					// 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
800
					out[ j ] = (byte)ch;
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
801
				j++;
1 by edam
Initial import
802
			}
18 by edam
- changed case on charset and encoding warning strings (it looked bad)
803
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
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 );
1 by edam
Initial import
827
		}
828
	}
829
}