├── aFileChooser ├── res │ ├── drawable │ │ ├── ic_file.png │ │ ├── ic_chooser.png │ │ └── ic_folder.png │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── layout │ │ ├── file.xml │ │ └── explorer.xml │ └── xml │ │ └── mimetypes.xml ├── .classpath ├── project.properties ├── proguard-project.txt ├── .project ├── AndroidManifest.xml ├── proguard.cfg ├── src │ └── com │ │ └── ipaulpro │ │ └── afilechooser │ │ ├── utils │ │ ├── MimeTypes.java │ │ ├── MimeTypeParser.java │ │ └── FileUtils.java │ │ ├── FileListAdapter.java │ │ └── FileChooserActivity.java └── build.xml ├── aFileChooserExample ├── res │ ├── values │ │ ├── styles.xml │ │ └── strings.xml │ └── values-v11 │ │ └── styles.xml ├── .classpath ├── project.properties ├── .project ├── AndroidManifest.xml ├── proguard.cfg └── src │ └── com │ └── ipaulpro │ └── afilechoosertest │ └── FileChooserTestActivity.java ├── .gitignore ├── README.markdown └── LICENSE.txt /aFileChooser/res/drawable/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowei/aFileChooser/HEAD/aFileChooser/res/drawable/ic_file.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowei/aFileChooser/HEAD/aFileChooser/res/drawable/ic_chooser.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuowei/aFileChooser/HEAD/aFileChooser/res/drawable/ic_folder.png -------------------------------------------------------------------------------- /aFileChooserExample/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /aFileChooserExample/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | aFileChooserExample 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /aFileChooser/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /aFileChooserExample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /aFileChooser/res/layout/file.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 27 | 28 | 33 | 34 | -------------------------------------------------------------------------------- /aFileChooser/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /aFileChooser/res/layout/explorer.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 26 | 27 | 34 | 35 | -------------------------------------------------------------------------------- /aFileChooserExample/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/utils/MimeTypes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 OpenIntents.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ipaulpro.afilechooser.utils; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import android.net.Uri; 23 | import android.webkit.MimeTypeMap; 24 | 25 | public class MimeTypes { 26 | 27 | private Map mMimeTypes; 28 | 29 | public MimeTypes() { 30 | mMimeTypes = new HashMap(); 31 | } 32 | 33 | public void put(String type, String extension) { 34 | // Convert extensions to lower case letters for easier comparison 35 | extension = extension.toLowerCase(); 36 | 37 | mMimeTypes.put(type, extension); 38 | } 39 | 40 | public String getMimeType(String filename) { 41 | 42 | String extension = FileUtils.getExtension(filename); 43 | 44 | // Let's check the official map first. Webkit has a nice extension-to-MIME map. 45 | // Be sure to remove the first character from the extension, which is the "." character. 46 | if (extension.length() > 0) { 47 | String webkitMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); 48 | 49 | if (webkitMimeType != null) { 50 | // Found one. Let's take it! 51 | return webkitMimeType; 52 | } 53 | } 54 | 55 | // Convert extensions to lower case letters for easier comparison 56 | extension = extension.toLowerCase(); 57 | 58 | String mimetype = mMimeTypes.get(extension); 59 | 60 | if(mimetype==null) mimetype = "*/*"; 61 | 62 | return mimetype; 63 | } 64 | 65 | public String getMimeType(Uri uri) { 66 | 67 | return getMimeType(FileUtils.getFile(uri).getName()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /aFileChooserExample/src/com/ipaulpro/afilechoosertest/FileChooserTestActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Paul Burke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ipaulpro.afilechoosertest; 18 | 19 | import java.io.File; 20 | 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.os.Bundle; 24 | import android.util.Log; 25 | import android.widget.Toast; 26 | 27 | import com.ipaulpro.afilechooser.FileChooserActivity; 28 | import com.ipaulpro.afilechooser.utils.FileUtils; 29 | 30 | /** 31 | * @author paulburke (ipaulpro) 32 | */ 33 | public class FileChooserTestActivity extends FileChooserActivity { 34 | // TAG for log messages. 35 | private static final String TAG = "FileSelectorTestActivity"; 36 | 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | if (Intent.ACTION_MAIN.equals(getIntent().getAction())) { 41 | // Display the file chooser dialog with default options. 42 | showFileChooser(); 43 | } 44 | } 45 | 46 | @Override 47 | protected void onFileSelect(File file) { 48 | if (file != null) { 49 | final Context context = getApplicationContext(); 50 | 51 | // Get the path of the Selected File. 52 | final String path = file.getAbsolutePath(); 53 | Toast.makeText(FileChooserTestActivity.this, 54 | "File Selected: "+path, Toast.LENGTH_LONG).show(); 55 | 56 | // Get the MIME type of the Selected File. 57 | String mimeType = FileUtils.getMimeType(context, file); 58 | Log.d(TAG, "File MIME type: " + mimeType); 59 | 60 | // Get the thumbnail of the Selected File, if image/video 61 | // final Uri uri = Uri.fromFile(file); 62 | // Bitmap bm = FileUtils.getThumbnail(context, uri, mimeType); 63 | 64 | finish(); 65 | } 66 | } 67 | 68 | @Override 69 | protected void onFileError(Exception e) { 70 | Log.e(TAG, "File select error", e); 71 | finish(); 72 | } 73 | 74 | @Override 75 | protected void onFileSelectCancel() { 76 | Log.d(TAG, "File selections canceled"); 77 | finish(); 78 | } 79 | 80 | @Override 81 | protected void onFileDisconnect() { 82 | Log.d(TAG, "External storage disconneted"); 83 | finish(); 84 | } 85 | } -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/utils/MimeTypeParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 OpenIntents.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ipaulpro.afilechooser.utils; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.InputStreamReader; 22 | 23 | import org.xmlpull.v1.XmlPullParser; 24 | import org.xmlpull.v1.XmlPullParserException; 25 | import org.xmlpull.v1.XmlPullParserFactory; 26 | 27 | import android.content.res.XmlResourceParser; 28 | 29 | public class MimeTypeParser { 30 | 31 | public static final String TAG_MIMETYPES = "MimeTypes"; 32 | public static final String TAG_TYPE = "type"; 33 | 34 | public static final String ATTR_EXTENSION = "extension"; 35 | public static final String ATTR_MIMETYPE = "mimetype"; 36 | 37 | private XmlPullParser mXpp; 38 | private MimeTypes mMimeTypes; 39 | 40 | public MimeTypeParser() { 41 | } 42 | 43 | public MimeTypes fromXml(InputStream in) 44 | throws XmlPullParserException, IOException { 45 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 46 | 47 | mXpp = factory.newPullParser(); 48 | mXpp.setInput(new InputStreamReader(in)); 49 | 50 | return parse(); 51 | } 52 | 53 | public MimeTypes fromXmlResource(XmlResourceParser in) 54 | throws XmlPullParserException, IOException { 55 | mXpp = in; 56 | 57 | return parse(); 58 | } 59 | 60 | public MimeTypes parse() 61 | throws XmlPullParserException, IOException { 62 | 63 | mMimeTypes = new MimeTypes(); 64 | 65 | int eventType = mXpp.getEventType(); 66 | 67 | while (eventType != XmlPullParser.END_DOCUMENT) { 68 | String tag = mXpp.getName(); 69 | 70 | if (eventType == XmlPullParser.START_TAG) { 71 | if (tag.equals(TAG_MIMETYPES)) { 72 | 73 | } else if (tag.equals(TAG_TYPE)) { 74 | addMimeTypeStart(); 75 | } 76 | } else if (eventType == XmlPullParser.END_TAG) { 77 | if (tag.equals(TAG_MIMETYPES)) { 78 | 79 | } 80 | } 81 | 82 | eventType = mXpp.next(); 83 | } 84 | 85 | return mMimeTypes; 86 | } 87 | 88 | private void addMimeTypeStart() { 89 | String extension = mXpp.getAttributeValue(null, ATTR_EXTENSION); 90 | String mimetype = mXpp.getAttributeValue(null, ATTR_MIMETYPE); 91 | 92 | mMimeTypes.put(extension, mimetype); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/FileListAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Paul Burke 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ipaulpro.afilechooser; 18 | 19 | import java.io.File; 20 | import java.util.ArrayList; 21 | 22 | import android.content.Context; 23 | import android.content.res.Resources; 24 | import android.graphics.drawable.Drawable; 25 | import android.view.LayoutInflater; 26 | import android.view.View; 27 | import android.view.ViewGroup; 28 | import android.widget.BaseAdapter; 29 | import android.widget.ImageView; 30 | import android.widget.LinearLayout; 31 | import android.widget.TextView; 32 | 33 | /** 34 | * @author paulburke (ipaulpro) 35 | */ 36 | public class FileListAdapter extends BaseAdapter { 37 | 38 | private final static int ICON_FOLDER = R.drawable.ic_folder; 39 | private final static int ICON_FILE = R.drawable.ic_file; 40 | 41 | private ArrayList mFiles = new ArrayList(); 42 | private LayoutInflater mInflater; 43 | 44 | public FileListAdapter(Context context) { 45 | mInflater = LayoutInflater.from(context); 46 | } 47 | 48 | public void setListItems(ArrayList files) { 49 | this.mFiles = files; 50 | } 51 | 52 | public int getCount() { 53 | return mFiles.size(); 54 | } 55 | 56 | public void add(File file) { 57 | mFiles.add(file); 58 | } 59 | 60 | public void clear() { 61 | mFiles.clear(); 62 | } 63 | 64 | public Object getItem(int position) { 65 | return mFiles.get(position); 66 | } 67 | 68 | public long getItemId(int position) { 69 | return position; 70 | } 71 | 72 | public View getView(int position, View convertView, ViewGroup parent) { 73 | View row = convertView; 74 | ViewHolder holder = null; 75 | 76 | if (row == null) { 77 | row = mInflater.inflate(R.layout.file, parent, false); 78 | holder = new ViewHolder(row); 79 | row.setTag(holder); 80 | } else { 81 | // Reduce, reuse, recycle! 82 | holder = (ViewHolder) row.getTag(); 83 | } 84 | 85 | // Get the file at the current position 86 | final File file = (File) getItem(position); 87 | 88 | // Set the TextView as the file name 89 | holder.nameView.setText(file.getName()); 90 | 91 | // If the item is not a directory, use the file icon 92 | holder.iconView.setImageResource(file.isDirectory() ? ICON_FOLDER 93 | : ICON_FILE); 94 | 95 | return row; 96 | } 97 | 98 | static class ViewHolder { 99 | TextView nameView; 100 | ImageView iconView; 101 | 102 | ViewHolder(View row) { 103 | nameView = (TextView) row.findViewById(R.id.file_name); 104 | iconView = (ImageView) row.findViewById(R.id.file_icon); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /aFileChooser/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /aFileChooser/res/xml/mimetypes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # aFileChooser - Android File Chooser 2 | 3 | Android developers often desire a way to present a user with a method of selecting a file from "external" storage. Android's `Intent` system gives developers the ability to implicitly hook into other app's components, but if the user doesn't have a file explorer installed, the developer must instruct them to install one, or build one, themselves. 4 | 5 | aFileChooser is an __Android Library Project__ that simplifies this process. 6 | 7 | Features: 8 | 9 | * Streamlines the `Intent.ACTION_GET_CONTENT` Intent calling process 10 | * Provides a built-in file explorer 11 | * Easily convert a URI into s java `File` object 12 | * Specify and determine MIME data types 13 | * Easily retrieve image thumbnails for media files 14 | * Follows Android conventions and is extremely simple to implement 15 | 16 | ## Installation 17 | 18 | Import aFileChooser and add it to your project as an Android Library Project. If you are unfamiliar with Android Library Projects, refer to the official documentation [here](http://developer.android.com/guide/developing/projects/projects-eclipse.html#ReferencingLibraryProject). 19 | 20 | Next, in your project, create an `Activity` that `extends `FileChooserActivity` and add it to your AndroidManifest.xml file. 21 | 22 | __Important__ The class extending `FileChooserActivity` must have the `intent-filter` set as seen bellow: 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Note: The `String` used for `android:label` will be shown on the `IntentChooser` dialog. 38 | 39 | ## Usage 40 | 41 | To initiate the file selection, simply call `showFileChooser()` and listen for the selected file by overriding `onFileSelect()`. E.g.: 42 | 43 | public class FileChooserTestActivity extends FileChooserActivity { 44 | @Override 45 | public void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | if (!isIntentGetContent()) { 48 | showFileChooser(); 49 | } 50 | } 51 | 52 | @Override 53 | protected void onFileSelect(File file) { 54 | // Here you handle the file selection. 55 | } 56 | } 57 | 58 | __Important__ - `FileChooserActivity` uses `Intent.ACTION_GET_CONTENT` to show the embedded file explorer. Your `Activity` must check the `Intent` `action`, to ensure that it is not `ACTION_GET_CONTENT`. 59 | 60 | ### A more robust implementation 61 | 62 | public class FileChooserTestActivity extends FileChooserActivity { 63 | // TAG for log messages. 64 | private static final String TAG = "FileSelectorTestActivity"; 65 | 66 | @Override 67 | public void onCreate(Bundle savedInstanceState) { 68 | super.onCreate(savedInstanceState); 69 | // We must check to ensure that the calling Intent is not Intent.ACTION_GET_CONTENT 70 | if (!isIntentGetContent()) { 71 | // Display the file chooser with all file types 72 | showFileChooser("*/*"); 73 | } 74 | } 75 | 76 | @Override 77 | protected void onFileSelect(File file) { 78 | if (file != null) { 79 | final Context context = getApplicationContext(); 80 | 81 | // Get the path of the Selected File. 82 | final String path = file.getAbsolutePath(); 83 | Log.d(TAG, "File path: " + path); 84 | 85 | // Get the MIME type of the Selected File. 86 | final String mimeType = FileUtils.getMimeType(context, file); 87 | Log.d(TAG, "File MIME type: " + mimeType); 88 | 89 | // Get the Uri of the Selected File 90 | // final Uri uri = Uri.fromFile(file); 91 | 92 | // Get the thumbnail of the Selected File, if image/video 93 | // final Bitmap bm = FileUtils.getThumbnail(context, uri, mimeType); 94 | 95 | // Here you can return any data from above to the calling Activity 96 | finish(); 97 | } 98 | } 99 | 100 | @Override 101 | protected void onFileError(Exception e) { 102 | Log.e(TAG, "File select error", e); 103 | finish(); 104 | } 105 | 106 | @Override 107 | protected void onFileSelectCancel() { 108 | Log.d(TAG, "File selections canceled"); 109 | finish(); 110 | } 111 | 112 | @Override 113 | protected void onFileDisconnect() { 114 | Log.d(TAG, "External storage disconneted"); 115 | finish(); 116 | } 117 | } 118 | 119 | ## Developed By 120 | 121 | Paul Burke [paulburke.co](http://paulburke.co/) 122 | 123 | ## License 124 | 125 | Copyright 2012 Paul Burke 126 | 127 | Licensed under the Apache License, Version 2.0 (the "License"); 128 | you may not use this file except in compliance with the License. 129 | You may obtain a copy of the License at 130 | 131 | http://www.apache.org/licenses/LICENSE-2.0 132 | 133 | Unless required by applicable law or agreed to in writing, software 134 | distributed under the License is distributed on an "AS IS" BASIS, 135 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136 | See the License for the specific language governing permissions and 137 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007-2008 OpenIntents.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ipaulpro.afilechooser.utils; 18 | 19 | import java.io.File; 20 | import java.io.FileFilter; 21 | import java.net.URISyntaxException; 22 | import java.text.DecimalFormat; 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.Comparator; 26 | import java.util.List; 27 | 28 | import android.content.ContentResolver; 29 | import android.content.Context; 30 | import android.content.Intent; 31 | import android.content.res.XmlResourceParser; 32 | import android.database.Cursor; 33 | import android.graphics.Bitmap; 34 | import android.net.Uri; 35 | import android.provider.MediaStore; 36 | import android.provider.MediaStore.Audio; 37 | import android.provider.MediaStore.Video; 38 | import android.util.Log; 39 | 40 | import com.ipaulpro.afilechooser.R; 41 | 42 | /** 43 | * @version 2009-07-03 44 | * 45 | * @author Peli 46 | * 47 | */ 48 | public class FileUtils { 49 | /** TAG for log messages. */ 50 | static final String TAG = "FileUtils"; 51 | private static final boolean DEBUG = false; // Set to true to enable logging 52 | 53 | public static final String MIME_TYPE_AUDIO = "audio/*"; 54 | public static final String MIME_TYPE_TEXT = "text/*"; 55 | public static final String MIME_TYPE_IMAGE = "image/*"; 56 | public static final String MIME_TYPE_VIDEO = "video/*"; 57 | public static final String MIME_TYPE_APP = "application/*"; 58 | 59 | public static final String EXTRA_MIME_TYPES = "net.zhuoweizhang.afilechooser.extra.MIME_TYPES"; 60 | public static final String EXTRA_SORT_METHOD = "net.zhuoweizhang.afilechooser.extra.SORT_METHOD"; 61 | public static final String SORT_LAST_MODIFIED = "net.zhuoweizhang.afilechooser.extra.SORT_LAST_MODIFIED"; 62 | 63 | /** 64 | * Whether the filename is a video file. 65 | * 66 | * @param filename 67 | * @return 68 | *//* 69 | public static boolean isVideo(String filename) { 70 | String mimeType = getMimeType(filename); 71 | if (mimeType != null && mimeType.startsWith("video/")) { 72 | return true; 73 | } else { 74 | return false; 75 | } 76 | }*/ 77 | 78 | /** 79 | * Whether the URI is a local one. 80 | * 81 | * @param uri 82 | * @return 83 | */ 84 | public static boolean isLocal(String uri) { 85 | if (uri != null && !uri.startsWith("http://")) { 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | /** 92 | * Gets the extension of a file name, like ".png" or ".jpg". 93 | * 94 | * @param uri 95 | * @return Extension including the dot("."); "" if there is no extension; 96 | * null if uri was null. 97 | */ 98 | public static String getExtension(String uri) { 99 | if (uri == null) { 100 | return null; 101 | } 102 | 103 | int dot = uri.lastIndexOf("."); 104 | if (dot >= 0) { 105 | return uri.substring(dot); 106 | } else { 107 | // No extension. 108 | return ""; 109 | } 110 | } 111 | 112 | /** 113 | * Returns true if uri is a media uri. 114 | * 115 | * @param uri 116 | * @return 117 | */ 118 | public static boolean isMediaUri(Uri uri) { 119 | String uriString = uri.toString(); 120 | if (uriString.startsWith(Audio.Media.INTERNAL_CONTENT_URI.toString()) 121 | || uriString.startsWith(Audio.Media.EXTERNAL_CONTENT_URI.toString()) 122 | || uriString.startsWith(Video.Media.INTERNAL_CONTENT_URI.toString()) 123 | || uriString.startsWith(Video.Media.EXTERNAL_CONTENT_URI.toString())) { 124 | return true; 125 | } else { 126 | return false; 127 | } 128 | } 129 | 130 | /** 131 | * Convert File into Uri. 132 | * @param file 133 | * @return uri 134 | */ 135 | public static Uri getUri(File file) { 136 | if (file != null) { 137 | return Uri.fromFile(file); 138 | } 139 | return null; 140 | } 141 | 142 | /** 143 | * Convert Uri into File. 144 | * @param uri 145 | * @return file 146 | */ 147 | public static File getFile(Uri uri) { 148 | if (uri != null) { 149 | String filepath = uri.getPath(); 150 | if (filepath != null) { 151 | return new File(filepath); 152 | } 153 | } 154 | return null; 155 | } 156 | 157 | /** 158 | * Returns the path only (without file name). 159 | * @param file 160 | * @return 161 | */ 162 | public static File getPathWithoutFilename(File file) { 163 | if (file != null) { 164 | if (file.isDirectory()) { 165 | // no file to be split off. Return everything 166 | return file; 167 | } else { 168 | String filename = file.getName(); 169 | String filepath = file.getAbsolutePath(); 170 | 171 | // Construct path without file name. 172 | String pathwithoutname = filepath.substring(0, filepath.length() - filename.length()); 173 | if (pathwithoutname.endsWith("/")) { 174 | pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1); 175 | } 176 | return new File(pathwithoutname); 177 | } 178 | } 179 | return null; 180 | } 181 | 182 | /** 183 | * Constructs a file from a path and file name. 184 | * 185 | * @param curdir 186 | * @param file 187 | * @return 188 | */ 189 | public static File getFile(String curdir, String file) { 190 | String separator = "/"; 191 | if (curdir.endsWith("/")) { 192 | separator = ""; 193 | } 194 | File clickedFile = new File(curdir + separator 195 | + file); 196 | return clickedFile; 197 | } 198 | 199 | public static File getFile(File curdir, String file) { 200 | return getFile(curdir.getAbsolutePath(), file); 201 | } 202 | 203 | 204 | /** 205 | * Get a file path from a Uri. 206 | * 207 | * @param context 208 | * @param uri 209 | * @return 210 | * @throws URISyntaxException 211 | * 212 | * @author paulburke 213 | */ 214 | public static String getPath(Context context, Uri uri) throws URISyntaxException { 215 | 216 | if(DEBUG) Log.d(TAG+" File -", 217 | "Authority: " + uri.getAuthority() + 218 | ", Fragment: " + uri.getFragment() + 219 | ", Port: " + uri.getPort() + 220 | ", Query: " + uri.getQuery() + 221 | ", Scheme: " + uri.getScheme() + 222 | ", Host: " + uri.getHost() + 223 | ", Segments: " + uri.getPathSegments().toString() 224 | ); 225 | 226 | if ("content".equalsIgnoreCase(uri.getScheme())) { 227 | String[] projection = { "_data" }; 228 | Cursor cursor = null; 229 | 230 | try { 231 | cursor = context.getContentResolver().query(uri, projection, null, null, null); 232 | int column_index = cursor 233 | .getColumnIndexOrThrow("_data"); 234 | if (cursor.moveToFirst()) { 235 | return cursor.getString(column_index); 236 | } 237 | } catch (Exception e) { 238 | // Eat it 239 | } 240 | } 241 | 242 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 243 | return uri.getPath(); 244 | } 245 | 246 | return null; 247 | } 248 | 249 | /** 250 | * Get the file size in a human-readable string. 251 | * 252 | * @param size 253 | * @return 254 | * 255 | * @author paulburke 256 | */ 257 | public static String getReadableFileSize(int size) { 258 | final int BYTES_IN_KILOBYTES = 1024; 259 | final DecimalFormat dec = new DecimalFormat("###.#"); 260 | final String KILOBYTES = " KB"; 261 | final String MEGABYTES = " MB"; 262 | final String GIGABYTES = " GB"; 263 | float fileSize = 0; 264 | String suffix = KILOBYTES; 265 | 266 | if (size > BYTES_IN_KILOBYTES) { 267 | fileSize = size/BYTES_IN_KILOBYTES; 268 | if (fileSize > BYTES_IN_KILOBYTES) { 269 | fileSize = fileSize/BYTES_IN_KILOBYTES; 270 | if (fileSize > BYTES_IN_KILOBYTES) { 271 | fileSize = fileSize/BYTES_IN_KILOBYTES; 272 | suffix = GIGABYTES; 273 | } else { 274 | suffix = MEGABYTES; 275 | } 276 | } 277 | } 278 | return String.valueOf(dec.format(fileSize)+suffix); 279 | } 280 | 281 | /** 282 | * Load MIME types from XML 283 | * 284 | * @param context 285 | * @return 286 | */ 287 | private static MimeTypes getMimeTypes(Context context) { 288 | MimeTypes mimeTypes = null; 289 | final MimeTypeParser mtp = new MimeTypeParser(); 290 | final XmlResourceParser in = context.getResources().getXml(R.xml.mimetypes); 291 | 292 | try { 293 | mimeTypes = mtp.fromXmlResource(in); 294 | } catch (Exception e) { 295 | if(DEBUG) Log.e(TAG, "getMimeTypes", e); 296 | } 297 | return mimeTypes; 298 | } 299 | 300 | /** 301 | * Get the file MIME type 302 | * 303 | * @param context 304 | * @param file 305 | * @return 306 | */ 307 | public static String getMimeType(Context context, File file) { 308 | String mimeType = null; 309 | final MimeTypes mimeTypes = getMimeTypes(context); 310 | if (file != null) mimeType = mimeTypes.getMimeType(file.getName()); 311 | return mimeType; 312 | } 313 | 314 | /** 315 | * Attempt to retrieve the thumbnail of given File from the MediaStore. 316 | * 317 | * This should not be called on the UI thread. 318 | * 319 | * @param context 320 | * @param file 321 | * @return 322 | * 323 | * @author paulburke 324 | */ 325 | public static Bitmap getThumbnail(Context context, File file) { 326 | return getThumbnail(context, getUri(file), getMimeType(context, file)); 327 | } 328 | 329 | /** 330 | * Attempt to retrieve the thumbnail of given Uri from the MediaStore. 331 | * 332 | * This should not be called on the UI thread. 333 | * 334 | * @param context 335 | * @param uri 336 | * @return 337 | * 338 | * @author paulburke 339 | */ 340 | public static Bitmap getThumbnail(Context context, Uri uri) { 341 | return getThumbnail(context, uri, getMimeType(context, getFile(uri))); 342 | } 343 | 344 | /** 345 | * Attempt to retrieve the thumbnail of given Uri from the MediaStore. 346 | * 347 | * This should not be called on the UI thread. 348 | * 349 | * @param context 350 | * @param uri 351 | * @param mimeType 352 | * @return 353 | * 354 | * @author paulburke 355 | */ 356 | public static Bitmap getThumbnail(Context context, Uri uri, String mimeType) { 357 | if(DEBUG) Log.d(TAG, "Attempting to get thumbnail"); 358 | 359 | if (isMediaUri(uri)) { 360 | Log.e(TAG, "You can only retrieve thumbnails for images and videos."); 361 | return null; 362 | } 363 | 364 | Bitmap bm = null; 365 | if (uri != null) { 366 | final ContentResolver resolver = context.getContentResolver(); 367 | Cursor cursor = null; 368 | try { 369 | cursor = resolver.query(uri, null, null,null, null); 370 | if (cursor.moveToFirst()) { 371 | final int id = cursor.getInt(0); 372 | if(DEBUG) Log.d(TAG, "Got thumb ID: "+id); 373 | 374 | if (mimeType.contains("video")) { 375 | bm = MediaStore.Video.Thumbnails.getThumbnail( 376 | resolver, 377 | id, 378 | MediaStore.Video.Thumbnails.MINI_KIND, 379 | null); 380 | } 381 | else if (mimeType.contains(FileUtils.MIME_TYPE_IMAGE)) { 382 | bm = MediaStore.Images.Thumbnails.getThumbnail( 383 | resolver, 384 | id, 385 | MediaStore.Images.Thumbnails.MINI_KIND, 386 | null); 387 | } 388 | } 389 | } catch (Exception e) { 390 | if(DEBUG) Log.e(TAG, "getThumbnail", e); 391 | } finally { 392 | if (cursor != null) cursor.close(); 393 | } 394 | } 395 | return bm; 396 | } 397 | 398 | private static final String HIDDEN_PREFIX = "."; 399 | 400 | /** 401 | * File and folder comparator. 402 | * TODO Expose sorting option method 403 | * 404 | * @author paulburke 405 | */ 406 | private static Comparator mComparator = new Comparator() { 407 | public int compare(File f1, File f2) { 408 | // Sort alphabetically by lower case, which is much cleaner 409 | return f1.getName().toLowerCase().compareTo( 410 | f2.getName().toLowerCase()); 411 | } 412 | }; 413 | 414 | /** 415 | * File (not directories) filter. 416 | * 417 | * @author paulburke 418 | */ 419 | private static FileFilter mFileFilter = new FileFilter() { 420 | public boolean accept(File file) { 421 | final String fileName = file.getName(); 422 | // Return files only (not directories) and skip hidden files 423 | return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX); 424 | } 425 | }; 426 | 427 | /** 428 | * Folder (directories) filter. 429 | * 430 | * @author paulburke 431 | */ 432 | private static FileFilter mDirFilter = new FileFilter() { 433 | public boolean accept(File file) { 434 | final String fileName = file.getName(); 435 | // Return directories only and skip hidden directories 436 | return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX); 437 | } 438 | }; 439 | 440 | /** 441 | * Get a list of Files in the give path 442 | * 443 | * @param path 444 | * @return Collection of files in give directory 445 | 446 | * @author paulburke 447 | */ 448 | public static List getFileList(String path) { 449 | ArrayList list = new ArrayList(); 450 | 451 | // Current directory File instance 452 | final File pathDir = new File(path); 453 | 454 | // List file in this directory with the directory filter 455 | final File[] dirs = pathDir.listFiles(mDirFilter); 456 | if (dirs != null) { 457 | // Sort the folders alphabetically 458 | Arrays.sort(dirs, mComparator); 459 | // Add each folder to the File list for the list adapter 460 | for (File dir : dirs) list.add(dir); 461 | } 462 | 463 | // List file in this directory with the file filter 464 | final File[] files = pathDir.listFiles(mFileFilter); 465 | if (files != null) { 466 | // Sort the files alphabetically 467 | Arrays.sort(files, mComparator); 468 | // Add each file to the File list for the list adapter 469 | for (File file : files) list.add(file); 470 | } 471 | 472 | return list; 473 | } 474 | 475 | /** 476 | * Get the Intent for selecting content to be used in an Intent Chooser. 477 | * 478 | * @return The intent for opening a file with Intent.createChooser() 479 | * 480 | * @author paulburke 481 | */ 482 | public static Intent createGetContentIntent() { 483 | // Implicitly allow the user to select a particular kind of data 484 | final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 485 | // The MIME data type filter 486 | intent.setType("*/*"); 487 | // Only return URIs that can be opened with ContentResolver 488 | intent.addCategory(Intent.CATEGORY_OPENABLE); 489 | return intent; 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/FileChooserActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Paul Burke 3 | * (Modified by Zhuowei Zhang for the PocketInvEditor project) 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.ipaulpro.afilechooser; 19 | 20 | import java.io.File; 21 | import java.io.FileFilter; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.Comparator; 25 | import java.util.HashSet; 26 | import java.util.Set; 27 | 28 | import android.app.ListActivity; 29 | import android.content.BroadcastReceiver; 30 | import android.content.Context; 31 | import android.content.Intent; 32 | import android.content.IntentFilter; 33 | import android.net.Uri; 34 | import android.os.Bundle; 35 | import android.os.Environment; 36 | import android.text.TextUtils; 37 | import android.util.Log; 38 | import android.view.View; 39 | import android.widget.BaseAdapter; 40 | import android.widget.ListView; 41 | 42 | import com.ipaulpro.afilechooser.utils.FileUtils; 43 | 44 | /** 45 | * @author paulburke (ipaulpro) 46 | */ 47 | public class FileChooserActivity extends ListActivity { 48 | 49 | private static final boolean DEBUG = true; // Set to false to disable logging 50 | private static final String TAG = "ChooserActivity"; // The log tag 51 | 52 | public static final int REQUEST_CODE = 6384; // onActivityResult request code 53 | public static final String MIME_TYPE_ALL = "*/*"; // Filter for all MIME types 54 | 55 | private static final String PATH = "path"; 56 | private static final String BREADCRUMB = "breadcrumb"; 57 | private static final String POSTIION = "position"; 58 | private static final String HIDDEN_PREFIX = "."; 59 | 60 | private String mPath; // The current file path 61 | private ArrayList mBreadcrumb = new ArrayList(); // Path history 62 | 63 | private boolean mExternalStorageAvailable = false; 64 | private boolean mExternalStorageWriteable = false; 65 | 66 | private File mExternalDir; 67 | private ArrayList mList = new ArrayList(); 68 | private Set extendedMimeTypes = new HashSet(); 69 | 70 | /** 71 | * File (not directories) filter. 72 | */ 73 | private FileFilter mFileFilter = new FileFilter() { 74 | public boolean accept(File file) { 75 | final String fileName = file.getName(); 76 | final String mimeType = FileUtils.getMimeType(FileChooserActivity.this, file); 77 | // Return files only (not directories) and skip hidden files 78 | return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX) && 79 | (mimeType.equals(getIntent().getType()) || extendedMimeTypes.contains(mimeType)); 80 | } 81 | }; 82 | 83 | /** 84 | * Folder (directories) filter. 85 | */ 86 | private FileFilter mDirFilter = new FileFilter() { 87 | public boolean accept(File file) { 88 | final String fileName = file.getName(); 89 | // Return directories only and skip hidden directories 90 | return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX); 91 | } 92 | }; 93 | 94 | /** 95 | * File and folder comparator. 96 | * TODO Expose sorting option method 97 | */ 98 | private Comparator mComparator = new Comparator() { 99 | public int compare(File f1, File f2) { 100 | // Sort alphabetically by lower case, which is much cleaner 101 | return f1.getName().toLowerCase().compareTo( 102 | f2.getName().toLowerCase()); 103 | } 104 | }; 105 | 106 | private Comparator mLastModifiedComparator = new Comparator() { 107 | public int compare(File f1, File f2) { 108 | long a = f1.lastModified(); 109 | long b = f2.lastModified(); 110 | if (a == b) return 0; 111 | return a < b? 1: -1; 112 | } 113 | }; 114 | 115 | /** 116 | * External storage state broadcast receiver. 117 | */ 118 | private BroadcastReceiver mExternalStorageReceiver = new BroadcastReceiver() { 119 | @Override 120 | public void onReceive(Context context, Intent intent) { 121 | if (DEBUG) Log.d(TAG, "External storage broadcast recieved: " 122 | + intent.getData()); 123 | updateExternalStorageState(); 124 | } 125 | }; 126 | 127 | 128 | /** 129 | * Activities extending FileChooserActivity must check against this, and implement 130 | * the associated Intent Filter in AndroidManifest.xml. 131 | * @return True if the Intent Action is android.intent.action.GET_CONTENT. 132 | */ 133 | protected boolean isIntentGetContent() { 134 | final Intent intent = getIntent(); 135 | final String action = intent.getAction(); 136 | if (DEBUG) Log.d(TAG, "Intent Action: "+action); 137 | return Intent.ACTION_GET_CONTENT.equals(action); 138 | } 139 | 140 | /** 141 | * Display the Intent Chooser. 142 | * @param title Chooser Dialog title. 143 | * @param type Explicit MIME data type filter. 144 | */ 145 | protected void showFileChooser(String title, String type) { 146 | if (TextUtils.isEmpty(title)) title = getString(R.string.select_file); 147 | if (TextUtils.isEmpty(type)) type = MIME_TYPE_ALL; 148 | 149 | // Implicitly allow the user to select a particular kind of data 150 | final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 151 | // Specify the MIME data type filter (Must be lower case) 152 | intent.setType(type.toLowerCase()); 153 | // Only return URIs that can be opened with ContentResolver 154 | intent.addCategory(Intent.CATEGORY_OPENABLE); 155 | // Display intent chooser 156 | try { 157 | startActivityForResult( 158 | Intent.createChooser(intent, title),REQUEST_CODE); 159 | } catch (android.content.ActivityNotFoundException e) { 160 | onFileError(e); 161 | } 162 | } 163 | 164 | /** 165 | * Convenience method to show the File Chooser with the default 166 | * title and have it return all file types. 167 | */ 168 | protected void showFileChooser() { 169 | showFileChooser(null, null); 170 | } 171 | 172 | /** 173 | * Fill the list with the current directory contents. 174 | */ 175 | private void fillList(int position) { 176 | if (DEBUG) Log.d(TAG, "Current path: "+this.mPath); 177 | 178 | // Set the cuttent path as the Activity title 179 | setTitle(this.mPath); 180 | // Clear the list adapter 181 | ((FileListAdapter) getListAdapter()).clear(); 182 | 183 | // Our current directory File instance 184 | final File pathDir = new File(mPath); 185 | 186 | // List file in this directory with the directory filter 187 | final File[] dirs = pathDir.listFiles(mDirFilter); 188 | if (dirs != null) { 189 | // Sort the folders alphabetically 190 | Arrays.sort(dirs, mComparator); 191 | // Add each folder to the File list for the list adapter 192 | for (File dir : dirs) mList.add(dir); 193 | } 194 | 195 | // List file in this directory with the file filter 196 | final File[] files = pathDir.listFiles(mFileFilter); 197 | if (files != null) { 198 | // Sort the files alphabetically 199 | Arrays.sort(files, mComparator); 200 | // Add each file to the File list for the list adapter 201 | for (File file : files) mList.add(file); 202 | } 203 | 204 | if (dirs == null && files == null) { 205 | if (DEBUG) Log.d(TAG, "Directory is empty"); 206 | } 207 | 208 | // Assign the File list items as our adapter items 209 | ((FileListAdapter) getListAdapter()).setListItems(mList); 210 | // Update the ListView 211 | ((FileListAdapter) getListAdapter()).notifyDataSetChanged(); 212 | // Jump to the top of the list 213 | getListView().setSelection(position); 214 | } 215 | 216 | /** 217 | * Keep track of the directory hierarchy. 218 | * @param add Add the current path to the directory stack. 219 | */ 220 | private void updateBreadcrumb(boolean add) { 221 | if (add) { 222 | // Add the current path to the stack 223 | this.mBreadcrumb.add(this.mPath); 224 | } else { 225 | if (this.mExternalDir.getAbsolutePath().equals(this.mPath)) { 226 | // If at the base directory, exit the Activity 227 | onFileSelectCancel(); 228 | finish(); 229 | } else { 230 | // Otherwise, remove the last path from the stack 231 | int size = this.mBreadcrumb.size(); 232 | if (size > 1) { 233 | this.mBreadcrumb.remove(size - 1); 234 | this.mPath = this.mBreadcrumb.get(size - 2); 235 | 236 | // Display the new directory contents 237 | fillList(0); 238 | } 239 | } 240 | } 241 | } 242 | 243 | /** 244 | * Update the external storage member variables. 245 | */ 246 | private void updateExternalStorageState() { 247 | String state = Environment.getExternalStorageState(); 248 | if (Environment.MEDIA_MOUNTED.equals(state)) { 249 | this.mExternalStorageAvailable = this.mExternalStorageWriteable = true; 250 | } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 251 | this.mExternalStorageAvailable = true; 252 | this.mExternalStorageWriteable = false; 253 | } else { 254 | this.mExternalStorageAvailable = this.mExternalStorageWriteable = false; 255 | } 256 | 257 | handleExternalStorageState(this.mExternalStorageAvailable, 258 | this.mExternalStorageWriteable); 259 | } 260 | 261 | /** 262 | * Register the external storage BroadcastReceiver. 263 | */ 264 | private void startWatchingExternalStorage() { 265 | IntentFilter filter = new IntentFilter(); 266 | filter.addAction(Intent.ACTION_MEDIA_MOUNTED); 267 | filter.addAction(Intent.ACTION_MEDIA_REMOVED); 268 | registerReceiver(this.mExternalStorageReceiver, filter); 269 | 270 | if (isIntentGetContent()) 271 | updateExternalStorageState(); 272 | } 273 | 274 | /** 275 | * Unregister the external storage BroadcastReceiver. 276 | */ 277 | private void stopWatchingExternalStorage() { 278 | unregisterReceiver(this.mExternalStorageReceiver); 279 | } 280 | 281 | /** 282 | * Respond to a change in the external storage state 283 | * @param available 284 | * @param writeable 285 | */ 286 | private void handleExternalStorageState(boolean available, boolean writeable) { 287 | if (!available && isIntentGetContent()) { 288 | if (DEBUG) Log.d(TAG, "External Storage was disconnected"); 289 | onFileDisconnect(); 290 | finish(); 291 | } 292 | } 293 | 294 | /** 295 | * Called when a file is successfully selected by the user. 296 | * @param file The file selected. 297 | */ 298 | protected void onFileSelect(File file){ 299 | if (DEBUG) Log.d(TAG, "File selected: "+file.getAbsolutePath()); 300 | } 301 | 302 | /** 303 | * Called when there is an error selecting a file. 304 | * @param e The error encountered during file selection. 305 | */ 306 | protected void onFileError(Exception e){ 307 | if (DEBUG) Log.e(TAG, "Error selecting file", e); 308 | } 309 | 310 | /** 311 | * Called when the user backs out of the file selection process. 312 | */ 313 | protected void onFileSelectCancel(){ 314 | if (DEBUG) Log.d(TAG, "File selection canceled"); 315 | } 316 | 317 | /** 318 | * Called when the external storage (SD) is disconnected. 319 | */ 320 | protected void onFileDisconnect(){ 321 | if (DEBUG) Log.d(TAG, "External storage disconnected"); 322 | } 323 | 324 | 325 | @Override 326 | protected void onCreate(Bundle savedInstanceState) { 327 | super.onCreate(savedInstanceState); 328 | 329 | String[] extraMimeTypes = getIntent().getStringArrayExtra(FileUtils.EXTRA_MIME_TYPES); 330 | extendedMimeTypes.clear(); 331 | if (extraMimeTypes != null) { 332 | for (String s: extraMimeTypes) 333 | extendedMimeTypes.add(s); 334 | } 335 | 336 | // Get the external storage directory. 337 | this.mExternalDir = Environment.getExternalStorageDirectory(); 338 | 339 | String sortMethod = getIntent().getStringExtra(FileUtils.EXTRA_SORT_METHOD); 340 | if (sortMethod != null) { 341 | if (sortMethod.equals(FileUtils.SORT_LAST_MODIFIED)) { 342 | this.mComparator = this.mLastModifiedComparator; 343 | } 344 | } 345 | 346 | if (getListAdapter() == null) { 347 | // Assign the list adapter to the ListView 348 | setListAdapter(new FileListAdapter(this)); 349 | } 350 | 351 | if (savedInstanceState != null) { 352 | restoreMe(savedInstanceState); 353 | } else { 354 | // Set the external storage directory as the current path 355 | this.mPath = this.mExternalDir.getAbsolutePath(); 356 | String startPath = getIntent().getStringExtra("startPath"); 357 | if (startPath != null) this.mPath = startPath; 358 | // Add the current path to the breadcrumb 359 | updateBreadcrumb(true); 360 | 361 | if (isIntentGetContent()) { 362 | setContentView(R.layout.explorer); 363 | fillList(0); 364 | } 365 | } 366 | } 367 | 368 | @Override 369 | protected void onResume() { 370 | super.onResume(); 371 | // Set the Broadcast Receiver to listen for storage mount changes 372 | startWatchingExternalStorage(); 373 | } 374 | 375 | @Override 376 | protected void onPause() { 377 | super.onPause(); 378 | // Remove the Broadcast Receiver listening for storage mount changes 379 | stopWatchingExternalStorage(); 380 | } 381 | 382 | @Override 383 | public void onBackPressed() { 384 | updateBreadcrumb(false); 385 | } 386 | 387 | @Override 388 | protected void onListItemClick(ListView l, View v, int position, long id) { 389 | super.onListItemClick(l, v, position, id); 390 | 391 | // Get the file that was selected from the file list 392 | File file = this.mList.get(position); 393 | // Save the path as our current member variable 394 | this.mPath = file.getAbsolutePath(); 395 | if (DEBUG) Log.d(TAG, "Selected file: "+this.mPath); 396 | 397 | if (file != null) { 398 | if (file.isDirectory()) { 399 | // If the selected item is a folder, update UI 400 | updateBreadcrumb(true); 401 | fillList(0); 402 | } else { 403 | // Otherwise, return the URI of the selected file 404 | final Intent data = new Intent(); 405 | data.setData(Uri.fromFile(file)); 406 | setResult(RESULT_OK, data); 407 | finish(); 408 | } 409 | } 410 | } 411 | 412 | @Override 413 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 414 | switch (requestCode) { 415 | case REQUEST_CODE: 416 | if (resultCode == RESULT_OK) { 417 | // If the file selection was successful 418 | try { 419 | // Get the URI of the selected file 420 | final Uri uri = data.getData(); 421 | // Create a file instance from the URI 422 | final File file = new File(FileUtils.getPath(this, uri)); 423 | // Expose the file 424 | onFileSelect(file); 425 | } catch (Exception e) { 426 | onFileError(e); 427 | } 428 | } else if (resultCode == RESULT_CANCELED) { 429 | onFileSelectCancel(); 430 | } 431 | break; 432 | } 433 | super.onActivityResult(requestCode, resultCode, data); 434 | } 435 | 436 | @Override 437 | protected void onSaveInstanceState(Bundle outState) { 438 | super.onSaveInstanceState(outState); 439 | // Save the current path and breadcrumb when the activity is interrupted. 440 | outState.putString(PATH, mPath); 441 | outState.putStringArrayList(BREADCRUMB, mBreadcrumb); 442 | outState.putInt(POSTIION, getListView().getFirstVisiblePosition()); 443 | } 444 | 445 | /** 446 | * If the activity was interrupted, restore the previous path and breadcrumb 447 | * @param savedInstanceState 448 | */ 449 | private void restoreMe(Bundle state) { 450 | // Restore the previous path. Defaults to base external storage dir 451 | this.mPath = (state.containsKey(PATH)) ? 452 | state.getString(PATH) : mExternalDir.getAbsolutePath(); 453 | // Restore the previous breadcrumb 454 | this.mBreadcrumb = state.getStringArrayList(BREADCRUMB); 455 | fillList(state.getInt(POSTIION)); 456 | } 457 | } 458 | --------------------------------------------------------------------------------