├── 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 |
4 |
5 |
--------------------------------------------------------------------------------
/aFileChooserExample/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/aFileChooserExample/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FileSelectorTest
5 | Choose file from SD
6 |
--------------------------------------------------------------------------------
/aFileChooser/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/aFileChooserExample/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS generated files #
2 | ######################
3 | .DS_Store*
4 | ehthumbs.db
5 | Icon?
6 | Thumbs.db
7 |
8 | # built application files
9 | *.apk
10 | *.ap_
11 |
12 | # files for the dex VM
13 | *.dex
14 |
15 | # Java class files
16 | *.class
17 |
18 | # generated files
19 | bin/
20 | gen/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 | org.eclipse.jdt.core.prefs
25 |
--------------------------------------------------------------------------------
/aFileChooser/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-10
12 | android.library=true
13 |
--------------------------------------------------------------------------------
/aFileChooserExample/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-16
12 | android.library.reference.1=../aFileChooser
13 |
--------------------------------------------------------------------------------
/aFileChooser/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | #eee
19 |
--------------------------------------------------------------------------------
/aFileChooser/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | AFileChooser
19 | Choose file from SD
20 |
--------------------------------------------------------------------------------
/aFileChooser/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/aFileChooser/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | aFileChooser
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/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 |
--------------------------------------------------------------------------------