/android/import-contacts

To get this branch, use:
bzr branch http://bzr.ed.am/android/import-contacts
19 by edam
- added file chooser
1
/*
2
 * FileChooser.java
3
 *
50 by edam
updated all URLs, email addresses and package names to ed.am
4
 * Copyright (C) 2010 Tim Marston <tim@ed.am>
19 by edam
- added file chooser
5
 *
6
 * This file is part of the Import Contacts program (hereafter referred
93 by Tim Marston
minor style tweaks
7
 * to as "this program").  For more information, see
50 by edam
updated all URLs, email addresses and package names to ed.am
8
 * http://ed.am/dev/android/import-contacts
19 by edam
- added file chooser
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
50 by edam
updated all URLs, email addresses and package names to ed.am
24
package am.ed.importcontacts;
19 by edam
- added file chooser
25
26
import java.io.File;
27
import java.io.FileFilter;
30 by edam
- determine path to SD card by querying android
28
import java.io.IOException;
19 by edam
- added file chooser
29
import java.util.ArrayList;
26 by edam
- take over orientation and keyboard hiden/shown config changes, to prevent our activities being restarted
30
import java.util.Collections;
31
import java.util.Comparator;
111 by Tim Marston
removed some unused code, fixed locale warnings and made showContinueOrAbort()
32
import java.util.Locale;
19 by edam
- added file chooser
33
34
import android.app.AlertDialog;
35
import android.app.Dialog;
36
import android.content.Context;
37
import android.content.DialogInterface;
30 by edam
- determine path to SD card by querying android
38
import android.os.Environment;
19 by edam
- added file chooser
39
import android.view.LayoutInflater;
40
import android.view.View;
41
import android.view.View.OnClickListener;
42
import android.view.ViewGroup;
43
import android.widget.AdapterView;
44
import android.widget.AdapterView.OnItemClickListener;
45
import android.widget.ArrayAdapter;
46
import android.widget.Button;
47
import android.widget.ImageView;
48
import android.widget.ListView;
49
import android.widget.TextView;
50
51
public class FileChooser
52
{
53
	// pick an existing directory
54
	public final static int MODE_DIR = 1;
55
56
	// pick an existing file
57
	public final static int MODE_FILE = 2;
58
59
60
	private Dialog _dialog;
61
62
	// mode
63
	private int _mode = MODE_DIR;
64
65
	// ok was pressed
66
	boolean _ok = false;
67
68
	// working path
69
	private String _path;
70
71
	// selected filename
72
	private String _filename;
73
74
	// enforce extension (in file-mode)
75
	private String[] _extensions;
76
77
	// path to secretly prefix all paths with
78
	private String _path_prefix = "";
79
80
	private Context _context;
81
	private ArrayList< RowItem > _items;
82
	private DialogInterface.OnDismissListener _on_dismiss_listener;
83
84
	// class that represents a row in the list
26 by edam
- take over orientation and keyboard hiden/shown config changes, to prevent our activities being restarted
85
	private class RowItem implements Comparable< RowItem >
19 by edam
- added file chooser
86
	{
87
		private String _name;
88
		private boolean _directory;
89
90
		public RowItem( String name, boolean directory )
91
		{
92
			_name = name;
93
			_directory = directory;
94
		}
95
96
		public String getName()
97
		{
98
			return _name;
99
		}
100
101
		public boolean isDirectory()
102
		{
103
			return _directory;
104
		}
26 by edam
- take over orientation and keyboard hiden/shown config changes, to prevent our activities being restarted
105
106
		@Override
107
		public int compareTo( RowItem that )
108
		{
109
			if( this._directory && !that._directory )
110
				return -1;
111
			else if( !this._directory && that._directory )
112
				return 1;
113
			else
114
				return this._name.compareToIgnoreCase( that._name );
115
		}
19 by edam
- added file chooser
116
	}
117
118
	// class to manage our list of RowItems
119
	private class RowItemAdapter extends ArrayAdapter< RowItem >
120
	{
121
		private ArrayList< RowItem > _items;
122
123
		public RowItemAdapter( Context context, int textview_resource_id,
124
			ArrayList< RowItem > items )
125
		{
126
			super( context, textview_resource_id, items );
127
			_items = items;
128
		}
129
130
		@Override
131
		public View getView( int position, View convert_view, ViewGroup parent )
132
		{
133
			View view = convert_view;
134
			if( view == null ) {
135
				LayoutInflater factory = LayoutInflater.from( _context );
136
				view = factory.inflate(  R.layout.filechooser_row, null );
137
			}
138
			RowItem rowitem = _items.get( position );
139
			if( rowitem != null ) {
140
				( (TextView)view.findViewById( R.id.name ) )
141
					.setText( rowitem.getName() );
142
				( (ImageView)view.findViewById( R.id.icon ) ).setVisibility(
143
					rowitem.isDirectory()? View.VISIBLE : View.GONE );
144
			}
145
			return view;
146
		}
147
	}
148
26 by edam
- take over orientation and keyboard hiden/shown config changes, to prevent our activities being restarted
149
	@SuppressWarnings( "serial" )
19 by edam
- added file chooser
150
	class InvalidPathPrefixException extends RuntimeException
151
	{
152
	}
153
154
155
156
	// constructor
20 by edam
- fixed bug where a file chooser wouldn't know it's context when the context was used
157
	public FileChooser( Context context )
19 by edam
- added file chooser
158
	{
20 by edam
- fixed bug where a file chooser wouldn't know it's context when the context was used
159
		_context = context;
19 by edam
- added file chooser
160
	}
161
162
	public void setMode( int mode )
163
	{
164
		_mode = mode;
165
	}
166
167
	public void setPath( String path )
168
	{
169
		_path = cleanUpPath( path );
170
		File file = new File( _path_prefix + path.trim() );
171
172
		// path and filename
173
		if( file.isFile() ) {
174
			_path = _path.substring( 0, _path.length() - 1 );
175
			_filename = _path.substring( _path.lastIndexOf( '/' ) + 1 );
176
			_path = _path.substring( 0, _path.length() - _filename.length() );
177
		}
178
179
		// else, treat as just a path
180
		else
181
			_filename = "";
182
	}
183
184
	public void setExtensions( String[] extensions )
185
	{
186
		_extensions = extensions;
187
	}
188
189
	// set dismiss listener
190
	public void setDismissListener(
191
		DialogInterface.OnDismissListener on_dismiss_listener )
192
	{
193
		_on_dismiss_listener = on_dismiss_listener;
194
	}
195
196
	// set the path prefix
197
	public void setPathPrefix( String path_prefix )
198
	{
26 by edam
- take over orientation and keyboard hiden/shown config changes, to prevent our activities being restarted
199
		// set to cleaned-up path, with trailing '/' removed so that it can be
19 by edam
- added file chooser
200
		// trivially pre-pended to a cleaned-up path
201
		_path_prefix = cleanUpPath( path_prefix );
202
		_path_prefix = _path_prefix.substring( 0, _path_prefix.length() - 1 );
203
	}
204
205
	public boolean getOk()
206
	{
207
		return _ok;
208
	}
209
210
	public String getPath()
211
	{
212
		return _path + _filename;
213
	}
214
20 by edam
- fixed bug where a file chooser wouldn't know it's context when the context was used
215
	public Dialog onCreateDialog()
19 by edam
- added file chooser
216
	{
217
		// custom layout in an AlertDialog
20 by edam
- fixed bug where a file chooser wouldn't know it's context when the context was used
218
		LayoutInflater factory = LayoutInflater.from( _context );
19 by edam
- added file chooser
219
		final View dialogView = factory.inflate(
220
			R.layout.filechooser, null );
221
222
		// wire up buttons
223
		( (Button)dialogView.findViewById( R.id.ok ) )
224
			.setOnClickListener( _fileChooserButtonListener );
225
		( (ListView)dialogView.findViewById( R.id.list ) )
226
			.setOnItemClickListener( _fileChooserItemClickListener );
227
228
		// return dialog
20 by edam
- fixed bug where a file chooser wouldn't know it's context when the context was used
229
		Dialog dialog = new AlertDialog.Builder( _context )
19 by edam
- added file chooser
230
			.setTitle( " " )
231
			.setView( dialogView )
232
			.create();
233
		dialog.setOnDismissListener( _on_dismiss_listener );
234
		return dialog;
235
	}
236
237
	private OnClickListener _fileChooserButtonListener = new OnClickListener() {
238
		public void onClick( View view )
239
		{
240
			switch( view.getId() )
241
			{
242
			case R.id.ok:
243
				// close dialog and free (don't keep a reference)
244
				_ok = true;
245
				_dialog.dismiss();
246
				break;
247
			}
248
		}
249
	};
250
93 by Tim Marston
minor style tweaks
251
	private OnItemClickListener _fileChooserItemClickListener =
252
			new OnItemClickListener() {
253
		public void onItemClick( AdapterView< ? > adapter_view, View view,
254
			int position, long id )
19 by edam
- added file chooser
255
		{
256
			RowItem rowitem = _items.get( position );
257
258
			// handle directory changes
259
			if( rowitem.isDirectory() )
260
			{
261
				String dirname = rowitem.getName();
262
				if( dirname.equals( ".." ) )
263
					strtipLastFilepartFromPath();
264
				else
265
					_path += dirname + "/";
266
				_filename = "";
267
268
				updateList();
269
			}
270
271
			// handle file selections
272
			else
273
			{
274
				_filename = rowitem.getName();
275
				updateCurrentSelection();
276
			}
277
		}
278
	};
279
280
	public void onPrepareDialog( Context context, Dialog dialog )
281
	{
282
		// set up reference to dialog
283
		_dialog = dialog;
284
		_context = context;
285
286
		// reset "ok"
287
		_ok = false;
288
289
		// pick text based on mode
290
		int title = 0, current = 0;
291
		switch( _mode ) {
292
		case MODE_DIR:
293
			title = R.string.filechooser_title_dir;
294
			current = R.string.filechooser_current_dir;
295
			break;
296
		case MODE_FILE:
297
			title = R.string.filechooser_title_file;
298
			current = R.string.filechooser_current_file;
299
			break;
300
		}
301
		dialog.setTitle( title );
302
		( (TextView)dialog.findViewById( R.id.current ) )
303
			.setText( _context.getString(  current ) );
304
305
		// clear filename in directory mode
306
		if( _mode == MODE_DIR )
307
			_filename = "";
308
309
		// set root path icon
310
		( (ImageView)_dialog.findViewById( R.id.icon ) )
311
			.setImageResource( pathIcon( cleanUpPath( _path_prefix ) ) );
312
313
		// setup current-path-specific stuff
314
		updateList();
315
	}
316
317
	public static String cleanUpPath( String path )
318
	{
319
		path = path.trim();
320
321
		// ensure it starts and ends in a '/'
322
		if( !path.startsWith( "/" ) ) path = "/" + path;
323
		if( !path.endsWith( "/" ) ) path += "/";
324
325
		return path;
326
	}
327
328
	public static int pathIcon( String path )
329
	{
30 by edam
- determine path to SD card by querying android
330
		// get sdcard path
331
		String sdcard_path;
332
		try {
333
			sdcard_path = Environment.getExternalStorageDirectory()
334
				.getCanonicalPath();
335
			if( sdcard_path.charAt( sdcard_path.length() - 1 ) != '/' )
336
				sdcard_path += "/";
337
		}
338
		catch( IOException e ) {
339
			sdcard_path = null;
340
		}
341
342
		// special paths
31 by edam
- missing check for null
343
		if( sdcard_path != null && path.equals( sdcard_path ) )
19 by edam
- added file chooser
344
			return R.drawable.sdcard;
345
30 by edam
- determine path to SD card by querying android
346
		// default
19 by edam
- added file chooser
347
		return R.drawable.directory;
348
	}
349
350
	public String prettyPrint( String full_path, boolean return_full )
351
	{
352
		String path = full_path;
353
30 by edam
- determine path to SD card by querying android
354
		// get sdcard path
355
		String sdcard_path;
356
		try {
357
			sdcard_path = Environment.getExternalStorageDirectory()
358
				.getCanonicalPath();
359
			if( sdcard_path.charAt( sdcard_path.length() - 1 ) != '/' )
360
				sdcard_path += "/";
361
		}
362
		catch( IOException e ) {
363
			sdcard_path = null;
364
		}
365
19 by edam
- added file chooser
366
		// special names
30 by edam
- determine path to SD card by querying android
367
		if( sdcard_path != null && path.equals( sdcard_path ) )
19 by edam
- added file chooser
368
			return " " + _context.getString( R.string.filechooser_path_sdcard );
369
370
		// remove prefix, if present
371
		if( path.startsWith( _path_prefix + "/" ) )
372
			path = path.substring( _path_prefix.length() );
373
93 by Tim Marston
minor style tweaks
374
		// unless path is "/", strip trailing "/"
19 by edam
- added file chooser
375
		if( path.length() > 1 && path.endsWith( "/" ) )
376
			path = path.substring( 0, path.length() - 1 );
377
378
		// if full path not required, strip off preceding directories
379
		if( !return_full ) {
380
			int idx = path.lastIndexOf( "/" );
381
			if( idx != -1 ) path = path.substring( idx + 1 );
382
		}
383
384
		return path;
385
	}
386
387
	protected void strtipLastFilepartFromPath()
388
	{
389
		int at = _path.lastIndexOf( '/', _path.length() - 2 );
390
		if( at != -1 ) _path = _path.substring( 0, at + 1 );
391
	}
392
393
	protected void updateList()
394
	{
395
		// reset item list
396
		_items = new ArrayList< RowItem >();
397
398
		// open directory (and ensure _path is a directory)
399
		File dir = new File( _path_prefix + _path );
400
		while( !dir.isDirectory() ) {
401
			if( _path == "/" )
402
				throw new InvalidPathPrefixException();
403
			strtipLastFilepartFromPath();
404
			dir = new File( _path_prefix + _path );
405
		}
406
407
		// add ".."?
408
		if( !_path.equals( "/" ) )
409
			_items.add( new RowItem( "..", true ) );
410
411
		// get directories
412
		class DirFilter implements FileFilter {
413
			public boolean accept( File file ) {
414
				return file.isDirectory() && file.getName().charAt( 0 ) != '.';
415
			}
416
		}
417
		File[] files = dir.listFiles( new DirFilter() );
29 by edam
- fixed NPE while listing files from a bad path
418
		if( files != null )
419
			for( int i = 0; i < files.length; i++ )
420
				_items.add( new RowItem( files[ i ].getName(), true ) );
19 by edam
- added file chooser
421
422
		// get files
423
		if( _mode == MODE_FILE )
424
		{
425
			class VCardFilter implements FileFilter {
426
				public boolean accept( File file ) {
427
					if( file.isDirectory() || file.getName().startsWith( "." ) )
428
						return false;
111 by Tim Marston
removed some unused code, fixed locale warnings and made showContinueOrAbort()
429
					String filename =
430
						file.getName().toLowerCase( Locale.ENGLISH );
19 by edam
- added file chooser
431
					for( int i = 0; i < _extensions.length; i++ )
432
						if( filename.endsWith( "." + _extensions[ i ] ) )
433
							return true;
434
					return false;
435
				}
436
			}
437
			files = dir.listFiles( new VCardFilter() );
29 by edam
- fixed NPE while listing files from a bad path
438
			if( files != null )
439
				for( int i = 0; i < files.length; i++ )
440
					_items.add( new RowItem( files[ i ].getName(), false ) );
19 by edam
- added file chooser
441
		}
442
26 by edam
- take over orientation and keyboard hiden/shown config changes, to prevent our activities being restarted
443
		// sort
444
		class RowItemSorter implements Comparator< RowItem > {
445
			@Override
446
			public int compare( RowItem lhs, RowItem rhs ) {
447
				return lhs.compareTo( rhs );
448
			}
449
		}
450
		Collections.sort( _items, new RowItemSorter() );
451
19 by edam
- added file chooser
452
		// setup directory list
453
		( (ListView)_dialog.findViewById( R.id.list ) ).setAdapter(
454
			new RowItemAdapter( _context, R.layout.filechooser_row,
455
				_items ) );
456
457
		updateCurrentSelection();
458
	}
459
460
	private void updateCurrentSelection()
461
	{
462
		// set current path
463
		( (TextView)_dialog.findViewById( R.id.path ) ).setText(
464
			prettyPrint( _path_prefix + _path + _filename, true ) );
465
466
		// enable/disable ok button
467
		if( _mode == MODE_FILE )
468
			_dialog.findViewById( R.id.ok ).setEnabled( _filename != "" );
21 by edam
- fixed bug in file chooser where "ok" button could remain disabled
469
		else
470
			_dialog.findViewById( R.id.ok ).setEnabled( true );
19 by edam
- added file chooser
471
	}
472
473
}