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