├── .gitignore ├── LICENSE ├── README.md ├── hook └── apply-filechooser-patch ├── patch └── android-platform-filechooser.patch ├── plugin.xml ├── src └── android │ ├── FileChooser.java │ └── filechooser │ ├── FileChooserActivity.java │ ├── FileListAdapter.java │ ├── FileListFragment.java │ ├── FileLoader.java │ ├── FileUtils.java │ ├── LocalStorageProvider.java │ ├── libs │ └── android-support-v4.jar │ └── res │ ├── drawable-hdpi │ ├── ic_chooser.png │ ├── ic_file.png │ ├── ic_folder.png │ └── ic_provider.png │ ├── drawable-mdpi │ ├── ic_chooser.png │ ├── ic_file.png │ ├── ic_folder.png │ └── ic_provider.png │ ├── drawable-xhdpi │ ├── ic_chooser.png │ ├── ic_file.png │ ├── ic_folder.png │ └── ic_provider.png │ ├── drawable-xxhdpi │ ├── ic_chooser.png │ ├── ic_file.png │ ├── ic_folder.png │ └── ic_provider.png │ ├── layout │ └── file.xml │ ├── values-ca │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-ga │ └── strings.xml │ ├── values-it │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-pl │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-v11 │ └── strings.xml │ ├── values-v19 │ └── bool.xml │ ├── values │ ├── bool.xml │ ├── dimens.xml │ └── styles.xml │ └── xml │ └── mimetypes.xml └── www └── filechooser.js /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | 31 | .DS_Store 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cesidio DiBenedetto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FileChooser Cordova plugin for Android (mainly KitKat) 2 | 3 | This plugin was created as a workaround for https://issues.apache.org/jira/browse/CB-5294 and https://code.google.com/p/android/issues/detail?id=62220. This plugin is mainly configured for Android 4.4 so I would recommend to continue to use the native file dialogs for earlier versions of Android. There might be issues with the plugin as I have not fully tested all possible scenarios on many devices, but I have installed it on a Nexus 5 and it worked fine. 4 | 5 | 6 | The core pieces of the code were taken from https://github.com/iPaulPro/aFileChooser. A huge thanks to him!!! All I did was write the plugin wrapper around it. Please take note of the second part of the `Setup` step from the aforementioned **[aFileChooser](https://github.com/iPaulPro/aFileChooser)**. 7 | ``` 8 | Note that like a ContentProvider, the DocumentProvider authority must be unique. 9 | You should change com.ianhanniballake.localstorage.documents in your Manifest, as 10 | well as the LocalStorageProvider.AUTHORITY field. 11 | ``` 12 | 13 | 14 | ### Installation 15 | ``` 16 | cordova plugin add https://github.com/cdibened/filechooser.git 17 | ``` 18 | 19 | ### Configuration 20 | 21 | You will have to `import your.package.name.R` into the following java files. 22 | 23 | ``` 24 | FileChooser.java 25 | FileChooserActivity.java 26 | FileListAdapter.java 27 | FileListFragment.java 28 | LocalStorageProvider.java 29 | ``` 30 | 31 | ### Usage 32 | 33 | The first argument, which will eventually be filechooser parameters such as multi-select, mime-types...., is currently ignored but must be passed in. 34 | 35 | ``` 36 | var success = function(data) { 37 | console.log( data.filepath ); 38 | }; 39 | 40 | var error = function(msg) { 41 | console.log( msg ); 42 | }; 43 | 44 | filechooser.open({},success,error); 45 | ``` 46 | 47 | ### Example 48 | 49 | ``` 50 | var success = function( data ) { 51 | var filepath = data.filepath; 52 | function win(r) { 53 | console.log("Code = " + r.responseCode); 54 | console.log("Response = " + r.response); 55 | console.log("Sent = " + r.bytesSent); 56 | } 57 | 58 | function fail(error) { 59 | console.log("An error has occurred: Code = " + error.code); 60 | console.log("upload error source " + error.source); 61 | console.log("upload error target " + error.target); 62 | } 63 | 64 | var uri = encodeURI("http://localhost/upload/processupload.php"); 65 | var options = new FileUploadOptions(); 66 | options.fileKey="file"; 67 | options.fileName=filepath.substr(filepath.lastIndexOf('/')+1); 68 | 69 | var ft = new FileTransfer(); 70 | ft.onprogress = function(progressEvent) { 71 | if (progressEvent.lengthComputable) { 72 | loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total); 73 | } 74 | else { 75 | loadingStatus.increment(); 76 | } 77 | }; 78 | 79 | ft.upload(filepath, uri, win, fail, options); 80 | }; 81 | 82 | var error = function( msg ) { 83 | console.log( msg ); 84 | }; 85 | 86 | 87 | 88 | if( device.platform.toLowerCase() === 'android' && device.version.indexOf( '4.4' ) === 0 ) { 89 | $('#fileinput').click( function(e) { 90 | filechooser.open( {}, success, error ); 91 | }); 92 | } 93 | ``` 94 | 95 | 96 | ### Next up 97 | - automatically `import your.package.name.R` in the java files 98 | - add support for parameters in the filechooser 99 | -------------------------------------------------------------------------------- /hook/apply-filechooser-patch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | patch_file='plugin/com.cesidiodibenedetto.filechooser/patch/android-platform-filechooser.patch' 5 | package="$(sed -rn '/^ /dev/null 12 | then 13 | echo "Missing 'patch' command." >&2 14 | exit 1 15 | fi 16 | 17 | if gen_patch | ( cd platforms/android && patch -p1 --dry-run --batch --forward --silent >/dev/null ) 18 | then 19 | gen_patch | ( cd platforms/android && patch -p1 --batch --forward -r - ) 20 | else 21 | echo "Skipping patches, they are already applied or broken." >&2 22 | fi 23 | 24 | -------------------------------------------------------------------------------- /patch/android-platform-filechooser.patch: -------------------------------------------------------------------------------- 1 | diff -ruw android.orig/src/com/cesidiodibenedetto/filechooser/FileChooser.java android/src/com/cesidiodibenedetto/filechooser/FileChooser.java 2 | --- android.orig/src/com/cesidiodibenedetto/filechooser/FileChooser.java 2015-06-22 18:55:47.000000000 +0200 3 | +++ android/src/com/cesidiodibenedetto/filechooser/FileChooser.java 2015-06-22 19:01:52.908404787 +0200 4 | @@ -22,6 +22,8 @@ 5 | 6 | import com.ipaulpro.afilechooser.utils.FileUtils; 7 | 8 | +import PACKAGE.R; 9 | + 10 | /** 11 | * FileChooser is a PhoneGap plugin that acts as polyfill for Android KitKat and web 12 | * applications that need support for 13 | diff -ruw android.orig/src/com/ianhanniballake/localstorage/LocalStorageProvider.java android/src/com/ianhanniballake/localstorage/LocalStorageProvider.java 14 | --- android.orig/src/com/ianhanniballake/localstorage/LocalStorageProvider.java 2015-06-22 18:55:47.000000000 +0200 15 | +++ android/src/com/ianhanniballake/localstorage/LocalStorageProvider.java 2015-06-22 19:01:52.900404713 +0200 16 | @@ -21,6 +21,8 @@ 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | 20 | +import PACKAGE.R; 21 | + 22 | public class LocalStorageProvider extends DocumentsProvider { 23 | 24 | public static final String AUTHORITY = "com.ianhanniballake.localstorage.documents"; 25 | diff -ruw android.orig/src/com/ipaulpro/afilechooser/FileChooserActivity.java android/src/com/ipaulpro/afilechooser/FileChooserActivity.java 26 | --- android.orig/src/com/ipaulpro/afilechooser/FileChooserActivity.java 2015-06-22 18:55:47.000000000 +0200 27 | +++ android/src/com/ipaulpro/afilechooser/FileChooserActivity.java 2015-06-22 18:59:58.815348192 +0200 28 | @@ -36,6 +36,8 @@ 29 | 30 | import java.io.File; 31 | 32 | +import PACKAGE.R; 33 | + 34 | /** 35 | * Main Activity that handles the FileListFragments 36 | * 37 | diff -ruw android.orig/src/com/ipaulpro/afilechooser/FileListAdapter.java android/src/com/ipaulpro/afilechooser/FileListAdapter.java 38 | --- android.orig/src/com/ipaulpro/afilechooser/FileListAdapter.java 2015-06-22 18:55:47.000000000 +0200 39 | +++ android/src/com/ipaulpro/afilechooser/FileListAdapter.java 2015-06-22 18:59:03.178832147 +0200 40 | @@ -27,6 +27,8 @@ 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | 44 | +import PACKAGE.R; 45 | + 46 | /** 47 | * List adapter for Files. 48 | * 49 | diff -ruw android.orig/src/com/ipaulpro/afilechooser/FileListFragment.java android/src/com/ipaulpro/afilechooser/FileListFragment.java 50 | --- android.orig/src/com/ipaulpro/afilechooser/FileListFragment.java 2015-06-22 18:55:47.000000000 +0200 51 | +++ android/src/com/ipaulpro/afilechooser/FileListFragment.java 2015-06-22 18:59:40.251176067 +0200 52 | @@ -28,6 +28,8 @@ 53 | import java.io.File; 54 | import java.util.List; 55 | 56 | +import PACKAGE.R; 57 | + 58 | /** 59 | * Fragment that displays a list of Files in a given path. 60 | * 61 | diff -ruw android.orig/src/com/ipaulpro/afilechooser/FileLoader.java android/src/com/ipaulpro/afilechooser/FileLoader.java 62 | --- android.orig/src/com/ipaulpro/afilechooser/FileLoader.java 2015-06-22 18:55:47.000000000 +0200 63 | +++ android/src/com/ipaulpro/afilechooser/FileLoader.java 2015-06-22 19:00:41.147740468 +0200 64 | @@ -27,6 +27,8 @@ 65 | import java.util.Arrays; 66 | import java.util.List; 67 | 68 | +import PACKAGE.R; 69 | + 70 | /** 71 | * Loader that returns a list of Files in a given file path. 72 | * 73 | diff -ruw android.orig/src/com/ipaulpro/afilechooser/utils/FileUtils.java android/src/com/ipaulpro/afilechooser/utils/FileUtils.java 74 | --- android.orig/src/com/ipaulpro/afilechooser/utils/FileUtils.java 2015-06-22 18:55:47.000000000 +0200 75 | +++ android/src/com/ipaulpro/afilechooser/utils/FileUtils.java 2015-06-22 19:00:49.395816864 +0200 76 | @@ -38,6 +38,8 @@ 77 | import java.text.DecimalFormat; 78 | import java.util.Comparator; 79 | 80 | +import PACKAGE.R; 81 | + 82 | /** 83 | * @version 2009-07-03 84 | * @author Peli 85 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | FileChooser 7 | File Chooser for Cordova to overcome Android 4.4 issues outlined here: https://issues.apache.org/jira/browse/CB-5294 and https://code.google.com/p/android/issues/detail?id=62220 8 | MIT 9 | cordova,filechooser,android 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Empty Directory 25 | Storage was removed or unmounted. 26 | Select a file 27 | File Browser 28 | Error selecting File 29 | Internal storage 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 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 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/android/FileChooser.java: -------------------------------------------------------------------------------- 1 | package com.cesidiodibenedetto.filechooser; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.cordova.CordovaActivity; 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import android.app.Activity; 12 | import android.content.ActivityNotFoundException; 13 | import android.content.Intent; 14 | import android.net.Uri; 15 | import android.os.Bundle; 16 | import android.util.Log; 17 | 18 | import org.apache.cordova.CallbackContext; 19 | import org.apache.cordova.CordovaPlugin; 20 | import org.apache.cordova.CordovaResourceApi; 21 | import org.apache.cordova.PluginResult; 22 | 23 | import com.ipaulpro.afilechooser.utils.FileUtils; 24 | 25 | /** 26 | * FileChooser is a PhoneGap plugin that acts as polyfill for Android KitKat and web 27 | * applications that need support for 28 | * 29 | */ 30 | public class FileChooser extends CordovaPlugin { 31 | 32 | private CallbackContext callbackContext = null; 33 | private static final String TAG = "FileChooser"; 34 | private static final int REQUEST_CODE = 6666; // onActivityResult request code 35 | 36 | private void showFileChooser() { 37 | // Use the GET_CONTENT intent from the utility class 38 | Intent target = FileUtils.createGetContentIntent(); 39 | // Create the chooser Intent 40 | Intent intent = Intent.createChooser( 41 | target, this.cordova.getActivity().getString(R.string.chooser_title)); 42 | try { 43 | this.cordova.startActivityForResult((CordovaPlugin) this, intent, REQUEST_CODE); 44 | } catch (ActivityNotFoundException e) { 45 | // The reason for the existence of aFileChooser 46 | } 47 | } 48 | 49 | @Override 50 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 51 | if( requestCode == REQUEST_CODE) { 52 | // If the file selection was successful 53 | if (resultCode == Activity.RESULT_OK) { 54 | if (data != null) { 55 | // Get the URI of the selected file 56 | final Uri uri = data.getData(); 57 | Log.i(TAG, "Uri = " + uri.toString()); 58 | JSONObject obj = new JSONObject(); 59 | try { 60 | // Get the file path from the URI 61 | final String path = FileUtils.getPath(this.cordova.getActivity(), uri); 62 | obj.put("filepath", path); 63 | this.callbackContext.success(obj); 64 | } catch (Exception e) { 65 | Log.e("FileChooser", "File select error", e); 66 | this.callbackContext.error(e.getMessage()); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | @Override 74 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { 75 | this.callbackContext = callbackContext; 76 | if (action.equals("open")) { 77 | showFileChooser(); 78 | return true; 79 | } 80 | else { 81 | return false; 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/android/filechooser/FileChooserActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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 android.app.ActionBar; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.IntentFilter; 24 | import android.net.Uri; 25 | import android.os.Build; 26 | import android.os.Bundle; 27 | import android.os.Environment; 28 | import android.support.v4.app.FragmentActivity; 29 | import android.support.v4.app.FragmentManager; 30 | import android.support.v4.app.FragmentManager.BackStackEntry; 31 | import android.support.v4.app.FragmentManager.OnBackStackChangedListener; 32 | import android.support.v4.app.FragmentTransaction; 33 | import android.view.Menu; 34 | import android.view.MenuItem; 35 | import android.widget.Toast; 36 | 37 | import java.io.File; 38 | 39 | /** 40 | * Main Activity that handles the FileListFragments 41 | * 42 | * @version 2013-06-25 43 | * @author paulburke (ipaulpro) 44 | */ 45 | public class FileChooserActivity extends FragmentActivity implements 46 | OnBackStackChangedListener, FileListFragment.Callbacks { 47 | 48 | public static final String PATH = "path"; 49 | public static final String EXTERNAL_BASE_PATH = Environment 50 | .getExternalStorageDirectory().getAbsolutePath(); 51 | 52 | private static final boolean HAS_ACTIONBAR = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; 53 | 54 | private FragmentManager mFragmentManager; 55 | private BroadcastReceiver mStorageListener = new BroadcastReceiver() { 56 | @Override 57 | public void onReceive(Context context, Intent intent) { 58 | Toast.makeText(context, R.string.storage_removed, Toast.LENGTH_LONG).show(); 59 | finishWithResult(null); 60 | } 61 | }; 62 | 63 | private String mPath; 64 | 65 | @Override 66 | protected void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | 69 | mFragmentManager = getSupportFragmentManager(); 70 | mFragmentManager.addOnBackStackChangedListener(this); 71 | 72 | if (savedInstanceState == null) { 73 | mPath = EXTERNAL_BASE_PATH; 74 | addFragment(); 75 | } else { 76 | mPath = savedInstanceState.getString(PATH); 77 | } 78 | 79 | setTitle(mPath); 80 | } 81 | 82 | @Override 83 | protected void onPause() { 84 | super.onPause(); 85 | 86 | unregisterStorageListener(); 87 | } 88 | 89 | @Override 90 | protected void onResume() { 91 | super.onResume(); 92 | 93 | registerStorageListener(); 94 | } 95 | 96 | @Override 97 | protected void onSaveInstanceState(Bundle outState) { 98 | super.onSaveInstanceState(outState); 99 | 100 | outState.putString(PATH, mPath); 101 | } 102 | 103 | @Override 104 | public void onBackStackChanged() { 105 | 106 | int count = mFragmentManager.getBackStackEntryCount(); 107 | if (count > 0) { 108 | BackStackEntry fragment = mFragmentManager.getBackStackEntryAt(count - 1); 109 | mPath = fragment.getName(); 110 | } else { 111 | mPath = EXTERNAL_BASE_PATH; 112 | } 113 | 114 | setTitle(mPath); 115 | if (HAS_ACTIONBAR) 116 | invalidateOptionsMenu(); 117 | } 118 | 119 | @Override 120 | public boolean onCreateOptionsMenu(Menu menu) { 121 | if (HAS_ACTIONBAR) { 122 | boolean hasBackStack = mFragmentManager.getBackStackEntryCount() > 0; 123 | 124 | ActionBar actionBar = getActionBar(); 125 | actionBar.setDisplayHomeAsUpEnabled(hasBackStack); 126 | actionBar.setHomeButtonEnabled(hasBackStack); 127 | } 128 | 129 | return true; 130 | } 131 | 132 | @Override 133 | public boolean onOptionsItemSelected(MenuItem item) { 134 | switch (item.getItemId()) { 135 | case android.R.id.home: 136 | mFragmentManager.popBackStack(); 137 | return true; 138 | } 139 | 140 | return super.onOptionsItemSelected(item); 141 | } 142 | 143 | /** 144 | * Add the initial Fragment with given path. 145 | */ 146 | private void addFragment() { 147 | FileListFragment fragment = FileListFragment.newInstance(mPath); 148 | mFragmentManager.beginTransaction() 149 | .add(android.R.id.content, fragment).commit(); 150 | } 151 | 152 | /** 153 | * "Replace" the existing Fragment with a new one using given path. We're 154 | * really adding a Fragment to the back stack. 155 | * 156 | * @param file The file (directory) to display. 157 | */ 158 | private void replaceFragment(File file) { 159 | mPath = file.getAbsolutePath(); 160 | 161 | FileListFragment fragment = FileListFragment.newInstance(mPath); 162 | mFragmentManager.beginTransaction() 163 | .replace(android.R.id.content, fragment) 164 | .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) 165 | .addToBackStack(mPath).commit(); 166 | } 167 | 168 | /** 169 | * Finish this Activity with a result code and URI of the selected file. 170 | * 171 | * @param file The file selected. 172 | */ 173 | private void finishWithResult(File file) { 174 | if (file != null) { 175 | Uri uri = Uri.fromFile(file); 176 | setResult(RESULT_OK, new Intent().setData(uri)); 177 | finish(); 178 | } else { 179 | setResult(RESULT_CANCELED); 180 | finish(); 181 | } 182 | } 183 | 184 | /** 185 | * Called when the user selects a File 186 | * 187 | * @param file The file that was selected 188 | */ 189 | @Override 190 | public void onFileSelected(File file) { 191 | if (file != null) { 192 | if (file.isDirectory()) { 193 | replaceFragment(file); 194 | } else { 195 | finishWithResult(file); 196 | } 197 | } else { 198 | Toast.makeText(FileChooserActivity.this, R.string.error_selecting_file, 199 | Toast.LENGTH_SHORT).show(); 200 | } 201 | } 202 | 203 | /** 204 | * Register the external storage BroadcastReceiver. 205 | */ 206 | private void registerStorageListener() { 207 | IntentFilter filter = new IntentFilter(); 208 | filter.addAction(Intent.ACTION_MEDIA_REMOVED); 209 | registerReceiver(mStorageListener, filter); 210 | } 211 | 212 | /** 213 | * Unregister the external storage BroadcastReceiver. 214 | */ 215 | private void unregisterStorageListener() { 216 | unregisterReceiver(mStorageListener); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/android/filechooser/FileListAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 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 android.content.Context; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.BaseAdapter; 24 | import android.widget.TextView; 25 | 26 | import java.io.File; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * List adapter for Files. 32 | * 33 | * @version 2013-12-11 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 final LayoutInflater mInflater; 42 | 43 | private List mData = new ArrayList(); 44 | 45 | public FileListAdapter(Context context) { 46 | mInflater = LayoutInflater.from(context); 47 | } 48 | 49 | public void add(File file) { 50 | mData.add(file); 51 | notifyDataSetChanged(); 52 | } 53 | 54 | public void remove(File file) { 55 | mData.remove(file); 56 | notifyDataSetChanged(); 57 | } 58 | 59 | public void insert(File file, int index) { 60 | mData.add(index, file); 61 | notifyDataSetChanged(); 62 | } 63 | 64 | public void clear() { 65 | mData.clear(); 66 | notifyDataSetChanged(); 67 | } 68 | 69 | @Override 70 | public File getItem(int position) { 71 | return mData.get(position); 72 | } 73 | 74 | @Override 75 | public long getItemId(int position) { 76 | return position; 77 | } 78 | 79 | @Override 80 | public int getCount() { 81 | return mData.size(); 82 | } 83 | 84 | public List getListItems() { 85 | return mData; 86 | } 87 | 88 | /** 89 | * Set the list items without notifying on the clear. This prevents loss of 90 | * scroll position. 91 | * 92 | * @param data 93 | */ 94 | public void setListItems(List data) { 95 | mData = data; 96 | notifyDataSetChanged(); 97 | } 98 | 99 | @Override 100 | public View getView(int position, View convertView, ViewGroup parent) { 101 | View row = convertView; 102 | 103 | if (row == null) 104 | row = mInflater.inflate(R.layout.file, parent, false); 105 | 106 | TextView view = (TextView) row; 107 | 108 | // Get the file at the current position 109 | final File file = getItem(position); 110 | 111 | // Set the TextView as the file name 112 | view.setText(file.getName()); 113 | 114 | // If the item is not a directory, use the file icon 115 | int icon = file.isDirectory() ? ICON_FOLDER : ICON_FILE; 116 | view.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); 117 | 118 | return row; 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/android/filechooser/FileListFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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 android.app.Activity; 20 | import android.os.Bundle; 21 | import android.os.Environment; 22 | import android.support.v4.app.ListFragment; 23 | import android.support.v4.app.LoaderManager; 24 | import android.support.v4.content.Loader; 25 | import android.view.View; 26 | import android.widget.ListView; 27 | 28 | import java.io.File; 29 | import java.util.List; 30 | 31 | /** 32 | * Fragment that displays a list of Files in a given path. 33 | * 34 | * @version 2013-12-11 35 | * @author paulburke (ipaulpro) 36 | */ 37 | public class FileListFragment extends ListFragment implements 38 | LoaderManager.LoaderCallbacks> { 39 | 40 | /** 41 | * Interface to listen for events. 42 | */ 43 | public interface Callbacks { 44 | /** 45 | * Called when a file is selected from the list. 46 | * 47 | * @param file The file selected 48 | */ 49 | public void onFileSelected(File file); 50 | } 51 | 52 | private static final int LOADER_ID = 0; 53 | 54 | private FileListAdapter mAdapter; 55 | private String mPath; 56 | 57 | private Callbacks mListener; 58 | 59 | /** 60 | * Create a new instance with the given file path. 61 | * 62 | * @param path The absolute path of the file (directory) to display. 63 | * @return A new Fragment with the given file path. 64 | */ 65 | public static FileListFragment newInstance(String path) { 66 | FileListFragment fragment = new FileListFragment(); 67 | Bundle args = new Bundle(); 68 | args.putString(FileChooserActivity.PATH, path); 69 | fragment.setArguments(args); 70 | 71 | return fragment; 72 | } 73 | 74 | @Override 75 | public void onAttach(Activity activity) { 76 | super.onAttach(activity); 77 | 78 | try { 79 | mListener = (Callbacks) activity; 80 | } catch (ClassCastException e) { 81 | throw new ClassCastException(activity.toString() 82 | + " must implement FileListFragment.Callbacks"); 83 | } 84 | } 85 | 86 | @Override 87 | public void onCreate(Bundle savedInstanceState) { 88 | super.onCreate(savedInstanceState); 89 | 90 | mAdapter = new FileListAdapter(getActivity()); 91 | mPath = getArguments() != null ? getArguments().getString( 92 | FileChooserActivity.PATH) : Environment 93 | .getExternalStorageDirectory().getAbsolutePath(); 94 | } 95 | 96 | @Override 97 | public void onActivityCreated(Bundle savedInstanceState) { 98 | setEmptyText(getString(R.string.empty_directory)); 99 | setListAdapter(mAdapter); 100 | setListShown(false); 101 | 102 | getLoaderManager().initLoader(LOADER_ID, null, this); 103 | 104 | super.onActivityCreated(savedInstanceState); 105 | } 106 | 107 | @Override 108 | public void onListItemClick(ListView l, View v, int position, long id) { 109 | FileListAdapter adapter = (FileListAdapter) l.getAdapter(); 110 | if (adapter != null) { 111 | File file = (File) adapter.getItem(position); 112 | mPath = file.getAbsolutePath(); 113 | mListener.onFileSelected(file); 114 | } 115 | } 116 | 117 | @Override 118 | public Loader> onCreateLoader(int id, Bundle args) { 119 | return new FileLoader(getActivity(), mPath); 120 | } 121 | 122 | @Override 123 | public void onLoadFinished(Loader> loader, List data) { 124 | mAdapter.setListItems(data); 125 | 126 | if (isResumed()) 127 | setListShown(true); 128 | else 129 | setListShownNoAnimation(true); 130 | } 131 | 132 | @Override 133 | public void onLoaderReset(Loader> loader) { 134 | mAdapter.clear(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/android/filechooser/FileLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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 android.content.Context; 20 | import android.os.FileObserver; 21 | import android.support.v4.content.AsyncTaskLoader; 22 | 23 | import com.ipaulpro.afilechooser.utils.FileUtils; 24 | 25 | import java.io.File; 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | /** 31 | * Loader that returns a list of Files in a given file path. 32 | * 33 | * @version 2013-12-11 34 | * @author paulburke (ipaulpro) 35 | */ 36 | public class FileLoader extends AsyncTaskLoader> { 37 | 38 | private static final int FILE_OBSERVER_MASK = FileObserver.CREATE 39 | | FileObserver.DELETE | FileObserver.DELETE_SELF 40 | | FileObserver.MOVED_FROM | FileObserver.MOVED_TO 41 | | FileObserver.MODIFY | FileObserver.MOVE_SELF; 42 | 43 | private FileObserver mFileObserver; 44 | 45 | private List mData; 46 | private String mPath; 47 | 48 | public FileLoader(Context context, String path) { 49 | super(context); 50 | this.mPath = path; 51 | } 52 | 53 | @Override 54 | public List loadInBackground() { 55 | 56 | ArrayList list = new ArrayList(); 57 | 58 | // Current directory File instance 59 | final File pathDir = new File(mPath); 60 | 61 | // List file in this directory with the directory filter 62 | final File[] dirs = pathDir.listFiles(FileUtils.sDirFilter); 63 | if (dirs != null) { 64 | // Sort the folders alphabetically 65 | Arrays.sort(dirs, FileUtils.sComparator); 66 | // Add each folder to the File list for the list adapter 67 | for (File dir : dirs) 68 | list.add(dir); 69 | } 70 | 71 | // List file in this directory with the file filter 72 | final File[] files = pathDir.listFiles(FileUtils.sFileFilter); 73 | if (files != null) { 74 | // Sort the files alphabetically 75 | Arrays.sort(files, FileUtils.sComparator); 76 | // Add each file to the File list for the list adapter 77 | for (File file : files) 78 | list.add(file); 79 | } 80 | 81 | return list; 82 | } 83 | 84 | @Override 85 | public void deliverResult(List data) { 86 | if (isReset()) { 87 | onReleaseResources(data); 88 | return; 89 | } 90 | 91 | List oldData = mData; 92 | mData = data; 93 | 94 | if (isStarted()) 95 | super.deliverResult(data); 96 | 97 | if (oldData != null && oldData != data) 98 | onReleaseResources(oldData); 99 | } 100 | 101 | @Override 102 | protected void onStartLoading() { 103 | if (mData != null) 104 | deliverResult(mData); 105 | 106 | if (mFileObserver == null) { 107 | mFileObserver = new FileObserver(mPath, FILE_OBSERVER_MASK) { 108 | @Override 109 | public void onEvent(int event, String path) { 110 | onContentChanged(); 111 | } 112 | }; 113 | } 114 | mFileObserver.startWatching(); 115 | 116 | if (takeContentChanged() || mData == null) 117 | forceLoad(); 118 | } 119 | 120 | @Override 121 | protected void onStopLoading() { 122 | cancelLoad(); 123 | } 124 | 125 | @Override 126 | protected void onReset() { 127 | onStopLoading(); 128 | 129 | if (mData != null) { 130 | onReleaseResources(mData); 131 | mData = null; 132 | } 133 | } 134 | 135 | @Override 136 | public void onCanceled(List data) { 137 | super.onCanceled(data); 138 | 139 | onReleaseResources(data); 140 | } 141 | 142 | protected void onReleaseResources(List data) { 143 | 144 | if (mFileObserver != null) { 145 | mFileObserver.stopWatching(); 146 | mFileObserver = null; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /src/android/filechooser/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 android.content.ContentResolver; 20 | import android.content.ContentUris; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.database.Cursor; 24 | import android.database.DatabaseUtils; 25 | import android.graphics.Bitmap; 26 | import android.net.Uri; 27 | import android.os.Build; 28 | import android.os.Environment; 29 | import android.provider.DocumentsContract; 30 | import android.provider.MediaStore; 31 | import android.util.Log; 32 | import android.webkit.MimeTypeMap; 33 | 34 | import com.ianhanniballake.localstorage.LocalStorageProvider; 35 | 36 | import java.io.File; 37 | import java.io.FileFilter; 38 | import java.text.DecimalFormat; 39 | import java.util.Comparator; 40 | 41 | /** 42 | * @version 2009-07-03 43 | * @author Peli 44 | * @version 2013-12-11 45 | * @author paulburke (ipaulpro) 46 | */ 47 | public class FileUtils { 48 | private FileUtils() {} //private constructor to enforce Singleton pattern 49 | 50 | /** TAG for log messages. */ 51 | static final String TAG = "FileUtils"; 52 | private static final boolean DEBUG = false; // Set to true to enable logging 53 | 54 | public static final String MIME_TYPE_AUDIO = "audio/*"; 55 | public static final String MIME_TYPE_TEXT = "text/*"; 56 | public static final String MIME_TYPE_IMAGE = "image/*"; 57 | public static final String MIME_TYPE_VIDEO = "video/*"; 58 | public static final String MIME_TYPE_APP = "application/*"; 59 | 60 | public static final String HIDDEN_PREFIX = "."; 61 | 62 | /** 63 | * Gets the extension of a file name, like ".png" or ".jpg". 64 | * 65 | * @param uri 66 | * @return Extension including the dot("."); "" if there is no extension; 67 | * null if uri was null. 68 | */ 69 | public static String getExtension(String uri) { 70 | if (uri == null) { 71 | return null; 72 | } 73 | 74 | int dot = uri.lastIndexOf("."); 75 | if (dot >= 0) { 76 | return uri.substring(dot); 77 | } else { 78 | // No extension. 79 | return ""; 80 | } 81 | } 82 | 83 | /** 84 | * @return Whether the URI is a local one. 85 | */ 86 | public static boolean isLocal(String url) { 87 | if (url != null && !url.startsWith("http://") && !url.startsWith("https://")) { 88 | return true; 89 | } 90 | return false; 91 | } 92 | 93 | /** 94 | * @return True if Uri is a MediaStore Uri. 95 | * @author paulburke 96 | */ 97 | public static boolean isMediaUri(Uri uri) { 98 | return "media".equalsIgnoreCase(uri.getAuthority()); 99 | } 100 | 101 | /** 102 | * Convert File into Uri. 103 | * 104 | * @param file 105 | * @return uri 106 | */ 107 | public static Uri getUri(File file) { 108 | if (file != null) { 109 | return Uri.fromFile(file); 110 | } 111 | return null; 112 | } 113 | 114 | /** 115 | * Returns the path only (without file name). 116 | * 117 | * @param file 118 | * @return 119 | */ 120 | public static File getPathWithoutFilename(File file) { 121 | if (file != null) { 122 | if (file.isDirectory()) { 123 | // no file to be split off. Return everything 124 | return file; 125 | } else { 126 | String filename = file.getName(); 127 | String filepath = file.getAbsolutePath(); 128 | 129 | // Construct path without file name. 130 | String pathwithoutname = filepath.substring(0, 131 | filepath.length() - filename.length()); 132 | if (pathwithoutname.endsWith("/")) { 133 | pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1); 134 | } 135 | return new File(pathwithoutname); 136 | } 137 | } 138 | return null; 139 | } 140 | 141 | /** 142 | * @return The MIME type for the given file. 143 | */ 144 | public static String getMimeType(File file) { 145 | 146 | String extension = getExtension(file.getName()); 147 | 148 | if (extension.length() > 0) 149 | return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); 150 | 151 | return "application/octet-stream"; 152 | } 153 | 154 | /** 155 | * @return The MIME type for the give Uri. 156 | */ 157 | public static String getMimeType(Context context, Uri uri) { 158 | File file = new File(getPath(context, uri)); 159 | return getMimeType(file); 160 | } 161 | 162 | /** 163 | * @param uri The Uri to check. 164 | * @return Whether the Uri authority is {@link LocalStorageProvider}. 165 | * @author paulburke 166 | */ 167 | public static boolean isLocalStorageDocument(Uri uri) { 168 | return LocalStorageProvider.AUTHORITY.equals(uri.getAuthority()); 169 | } 170 | 171 | /** 172 | * @param uri The Uri to check. 173 | * @return Whether the Uri authority is ExternalStorageProvider. 174 | * @author paulburke 175 | */ 176 | public static boolean isExternalStorageDocument(Uri uri) { 177 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 178 | } 179 | 180 | /** 181 | * @param uri The Uri to check. 182 | * @return Whether the Uri authority is DownloadsProvider. 183 | * @author paulburke 184 | */ 185 | public static boolean isDownloadsDocument(Uri uri) { 186 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 187 | } 188 | 189 | /** 190 | * @param uri The Uri to check. 191 | * @return Whether the Uri authority is MediaProvider. 192 | * @author paulburke 193 | */ 194 | public static boolean isMediaDocument(Uri uri) { 195 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 196 | } 197 | 198 | /** 199 | * @param uri The Uri to check. 200 | * @return Whether the Uri authority is Google Photos. 201 | */ 202 | public static boolean isGooglePhotosUri(Uri uri) { 203 | return "com.google.android.apps.photos.content".equals(uri.getAuthority()); 204 | } 205 | 206 | /** 207 | * Get the value of the data column for this Uri. This is useful for 208 | * MediaStore Uris, and other file-based ContentProviders. 209 | * 210 | * @param context The context. 211 | * @param uri The Uri to query. 212 | * @param selection (Optional) Filter used in the query. 213 | * @param selectionArgs (Optional) Selection arguments used in the query. 214 | * @return The value of the _data column, which is typically a file path. 215 | * @author paulburke 216 | */ 217 | public static String getDataColumn(Context context, Uri uri, String selection, 218 | String[] selectionArgs) { 219 | 220 | Cursor cursor = null; 221 | final String column = "_data"; 222 | final String[] projection = { 223 | column 224 | }; 225 | 226 | try { 227 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 228 | null); 229 | if (cursor != null && cursor.moveToFirst()) { 230 | if (DEBUG) 231 | DatabaseUtils.dumpCursor(cursor); 232 | 233 | final int column_index = cursor.getColumnIndexOrThrow(column); 234 | return cursor.getString(column_index); 235 | } 236 | } finally { 237 | if (cursor != null) 238 | cursor.close(); 239 | } 240 | return null; 241 | } 242 | 243 | /** 244 | * Get a file path from a Uri. This will get the the path for Storage Access 245 | * Framework Documents, as well as the _data field for the MediaStore and 246 | * other file-based ContentProviders.
247 | *
248 | * Callers should check whether the path is local before assuming it 249 | * represents a local file. 250 | * 251 | * @param context The context. 252 | * @param uri The Uri to query. 253 | * @see #isLocal(String) 254 | * @see #getFile(Context, Uri) 255 | * @author paulburke 256 | */ 257 | public static String getPath(final Context context, final Uri uri) { 258 | 259 | if (DEBUG) 260 | Log.d(TAG + " File -", 261 | "Authority: " + uri.getAuthority() + 262 | ", Fragment: " + uri.getFragment() + 263 | ", Port: " + uri.getPort() + 264 | ", Query: " + uri.getQuery() + 265 | ", Scheme: " + uri.getScheme() + 266 | ", Host: " + uri.getHost() + 267 | ", Segments: " + uri.getPathSegments().toString() 268 | ); 269 | 270 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 271 | 272 | // DocumentProvider 273 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 274 | // LocalStorageProvider 275 | if (isLocalStorageDocument(uri)) { 276 | // The path is the id 277 | return DocumentsContract.getDocumentId(uri); 278 | } 279 | // ExternalStorageProvider 280 | else if (isExternalStorageDocument(uri)) { 281 | final String docId = DocumentsContract.getDocumentId(uri); 282 | final String[] split = docId.split(":"); 283 | final String type = split[0]; 284 | 285 | if ("primary".equalsIgnoreCase(type)) { 286 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 287 | } 288 | 289 | // TODO handle non-primary volumes 290 | } 291 | // DownloadsProvider 292 | else if (isDownloadsDocument(uri)) { 293 | 294 | final String id = DocumentsContract.getDocumentId(uri); 295 | final Uri contentUri = ContentUris.withAppendedId( 296 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 297 | 298 | return getDataColumn(context, contentUri, null, null); 299 | } 300 | // MediaProvider 301 | else if (isMediaDocument(uri)) { 302 | final String docId = DocumentsContract.getDocumentId(uri); 303 | final String[] split = docId.split(":"); 304 | final String type = split[0]; 305 | 306 | Uri contentUri = null; 307 | if ("image".equals(type)) { 308 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 309 | } else if ("video".equals(type)) { 310 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 311 | } else if ("audio".equals(type)) { 312 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 313 | } 314 | 315 | final String selection = "_id=?"; 316 | final String[] selectionArgs = new String[] { 317 | split[1] 318 | }; 319 | 320 | return getDataColumn(context, contentUri, selection, selectionArgs); 321 | } 322 | } 323 | // MediaStore (and general) 324 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 325 | 326 | // Return the remote address 327 | if (isGooglePhotosUri(uri)) 328 | return uri.getLastPathSegment(); 329 | 330 | return getDataColumn(context, uri, null, null); 331 | } 332 | // File 333 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 334 | return uri.getPath(); 335 | } 336 | 337 | return null; 338 | } 339 | 340 | /** 341 | * Convert Uri into File, if possible. 342 | * 343 | * @return file A local file that the Uri was pointing to, or null if the 344 | * Uri is unsupported or pointed to a remote resource. 345 | * @see #getPath(Context, Uri) 346 | * @author paulburke 347 | */ 348 | public static File getFile(Context context, Uri uri) { 349 | if (uri != null) { 350 | String path = getPath(context, uri); 351 | if (path != null && isLocal(path)) { 352 | return new File(path); 353 | } 354 | } 355 | return null; 356 | } 357 | 358 | /** 359 | * Get the file size in a human-readable string. 360 | * 361 | * @param size 362 | * @return 363 | * @author paulburke 364 | */ 365 | public static String getReadableFileSize(int size) { 366 | final int BYTES_IN_KILOBYTES = 1024; 367 | final DecimalFormat dec = new DecimalFormat("###.#"); 368 | final String KILOBYTES = " KB"; 369 | final String MEGABYTES = " MB"; 370 | final String GIGABYTES = " GB"; 371 | float fileSize = 0; 372 | String suffix = KILOBYTES; 373 | 374 | if (size > BYTES_IN_KILOBYTES) { 375 | fileSize = size / BYTES_IN_KILOBYTES; 376 | if (fileSize > BYTES_IN_KILOBYTES) { 377 | fileSize = fileSize / BYTES_IN_KILOBYTES; 378 | if (fileSize > BYTES_IN_KILOBYTES) { 379 | fileSize = fileSize / BYTES_IN_KILOBYTES; 380 | suffix = GIGABYTES; 381 | } else { 382 | suffix = MEGABYTES; 383 | } 384 | } 385 | } 386 | return String.valueOf(dec.format(fileSize) + suffix); 387 | } 388 | 389 | /** 390 | * Attempt to retrieve the thumbnail of given File from the MediaStore. This 391 | * should not be called on the UI thread. 392 | * 393 | * @param context 394 | * @param file 395 | * @return 396 | * @author paulburke 397 | */ 398 | public static Bitmap getThumbnail(Context context, File file) { 399 | return getThumbnail(context, getUri(file), getMimeType(file)); 400 | } 401 | 402 | /** 403 | * Attempt to retrieve the thumbnail of given Uri from the MediaStore. This 404 | * should not be called on the UI thread. 405 | * 406 | * @param context 407 | * @param uri 408 | * @return 409 | * @author paulburke 410 | */ 411 | public static Bitmap getThumbnail(Context context, Uri uri) { 412 | return getThumbnail(context, uri, getMimeType(context, uri)); 413 | } 414 | 415 | /** 416 | * Attempt to retrieve the thumbnail of given Uri from the MediaStore. This 417 | * should not be called on the UI thread. 418 | * 419 | * @param context 420 | * @param uri 421 | * @param mimeType 422 | * @return 423 | * @author paulburke 424 | */ 425 | public static Bitmap getThumbnail(Context context, Uri uri, String mimeType) { 426 | if (DEBUG) 427 | Log.d(TAG, "Attempting to get thumbnail"); 428 | 429 | if (!isMediaUri(uri)) { 430 | Log.e(TAG, "You can only retrieve thumbnails for images and videos."); 431 | return null; 432 | } 433 | 434 | Bitmap bm = null; 435 | if (uri != null) { 436 | final ContentResolver resolver = context.getContentResolver(); 437 | Cursor cursor = null; 438 | try { 439 | cursor = resolver.query(uri, null, null, null, null); 440 | if (cursor.moveToFirst()) { 441 | final int id = cursor.getInt(0); 442 | if (DEBUG) 443 | Log.d(TAG, "Got thumb ID: " + id); 444 | 445 | if (mimeType.contains("video")) { 446 | bm = MediaStore.Video.Thumbnails.getThumbnail( 447 | resolver, 448 | id, 449 | MediaStore.Video.Thumbnails.MINI_KIND, 450 | null); 451 | } 452 | else if (mimeType.contains(FileUtils.MIME_TYPE_IMAGE)) { 453 | bm = MediaStore.Images.Thumbnails.getThumbnail( 454 | resolver, 455 | id, 456 | MediaStore.Images.Thumbnails.MINI_KIND, 457 | null); 458 | } 459 | } 460 | } catch (Exception e) { 461 | if (DEBUG) 462 | Log.e(TAG, "getThumbnail", e); 463 | } finally { 464 | if (cursor != null) 465 | cursor.close(); 466 | } 467 | } 468 | return bm; 469 | } 470 | 471 | /** 472 | * File and folder comparator. TODO Expose sorting option method 473 | * 474 | * @author paulburke 475 | */ 476 | public static Comparator sComparator = new Comparator() { 477 | @Override 478 | public int compare(File f1, File f2) { 479 | // Sort alphabetically by lower case, which is much cleaner 480 | return f1.getName().toLowerCase().compareTo( 481 | f2.getName().toLowerCase()); 482 | } 483 | }; 484 | 485 | /** 486 | * File (not directories) filter. 487 | * 488 | * @author paulburke 489 | */ 490 | public static FileFilter sFileFilter = new FileFilter() { 491 | @Override 492 | public boolean accept(File file) { 493 | final String fileName = file.getName(); 494 | // Return files only (not directories) and skip hidden files 495 | return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX); 496 | } 497 | }; 498 | 499 | /** 500 | * Folder (directories) filter. 501 | * 502 | * @author paulburke 503 | */ 504 | public static FileFilter sDirFilter = new FileFilter() { 505 | @Override 506 | public boolean accept(File file) { 507 | final String fileName = file.getName(); 508 | // Return directories only and skip hidden directories 509 | return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX); 510 | } 511 | }; 512 | 513 | /** 514 | * Get the Intent for selecting content to be used in an Intent Chooser. 515 | * 516 | * @return The intent for opening a file with Intent.createChooser() 517 | * @author paulburke 518 | */ 519 | public static Intent createGetContentIntent() { 520 | // Implicitly allow the user to select a particular kind of data 521 | final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 522 | // The MIME data type filter 523 | intent.setType("*/*"); 524 | // Only return URIs that can be opened with ContentResolver 525 | intent.addCategory(Intent.CATEGORY_OPENABLE); 526 | return intent; 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /src/android/filechooser/LocalStorageProvider.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ianhanniballake.localstorage; 3 | 4 | import android.content.res.AssetFileDescriptor; 5 | import android.database.Cursor; 6 | import android.database.MatrixCursor; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Point; 10 | import android.os.CancellationSignal; 11 | import android.os.Environment; 12 | import android.os.ParcelFileDescriptor; 13 | import android.provider.DocumentsContract.Document; 14 | import android.provider.DocumentsContract.Root; 15 | import android.provider.DocumentsProvider; 16 | import android.util.Log; 17 | import android.webkit.MimeTypeMap; 18 | 19 | import java.io.File; 20 | import java.io.FileNotFoundException; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | 24 | public class LocalStorageProvider extends DocumentsProvider { 25 | 26 | public static final String AUTHORITY = "com.ianhanniballake.localstorage.documents"; 27 | 28 | /** 29 | * Default root projection: everything but Root.COLUMN_MIME_TYPES 30 | */ 31 | private final static String[] DEFAULT_ROOT_PROJECTION = new String[] { 32 | Root.COLUMN_ROOT_ID, 33 | Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON, 34 | Root.COLUMN_AVAILABLE_BYTES 35 | }; 36 | /** 37 | * Default document projection: everything but Document.COLUMN_ICON and 38 | * Document.COLUMN_SUMMARY 39 | */ 40 | private final static String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 41 | Document.COLUMN_DOCUMENT_ID, 42 | Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS, Document.COLUMN_MIME_TYPE, 43 | Document.COLUMN_SIZE, 44 | Document.COLUMN_LAST_MODIFIED 45 | }; 46 | 47 | @Override 48 | public Cursor queryRoots(final String[] projection) throws FileNotFoundException { 49 | // Create a cursor with either the requested fields, or the default 50 | // projection if "projection" is null. 51 | final MatrixCursor result = new MatrixCursor(projection != null ? projection 52 | : DEFAULT_ROOT_PROJECTION); 53 | // Add Home directory 54 | File homeDir = Environment.getExternalStorageDirectory(); 55 | final MatrixCursor.RowBuilder row = result.newRow(); 56 | // These columns are required 57 | row.add(Root.COLUMN_ROOT_ID, homeDir.getAbsolutePath()); 58 | row.add(Root.COLUMN_DOCUMENT_ID, homeDir.getAbsolutePath()); 59 | row.add(Root.COLUMN_TITLE, getContext().getString(R.string.internal_storage)); 60 | row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE); 61 | row.add(Root.COLUMN_ICON, R.drawable.ic_provider); 62 | // These columns are optional 63 | row.add(Root.COLUMN_AVAILABLE_BYTES, homeDir.getFreeSpace()); 64 | // Root.COLUMN_MIME_TYPE is another optional column and useful if you 65 | // have multiple roots with different 66 | // types of mime types (roots that don't match the requested mime type 67 | // are automatically hidden) 68 | return result; 69 | } 70 | 71 | @Override 72 | public String createDocument(final String parentDocumentId, final String mimeType, 73 | final String displayName) throws FileNotFoundException { 74 | File newFile = new File(parentDocumentId, displayName); 75 | try { 76 | newFile.createNewFile(); 77 | return newFile.getAbsolutePath(); 78 | } catch (IOException e) { 79 | Log.e(LocalStorageProvider.class.getSimpleName(), "Error creating new file " + newFile); 80 | } 81 | return null; 82 | } 83 | 84 | @Override 85 | public AssetFileDescriptor openDocumentThumbnail(final String documentId, final Point sizeHint, 86 | final CancellationSignal signal) throws FileNotFoundException { 87 | // Assume documentId points to an image file. Build a thumbnail no 88 | // larger than twice the sizeHint 89 | BitmapFactory.Options options = new BitmapFactory.Options(); 90 | options.inJustDecodeBounds = true; 91 | BitmapFactory.decodeFile(documentId, options); 92 | final int targetHeight = 2 * sizeHint.y; 93 | final int targetWidth = 2 * sizeHint.x; 94 | final int height = options.outHeight; 95 | final int width = options.outWidth; 96 | options.inSampleSize = 1; 97 | if (height > targetHeight || width > targetWidth) { 98 | final int halfHeight = height / 2; 99 | final int halfWidth = width / 2; 100 | // Calculate the largest inSampleSize value that is a power of 2 and 101 | // keeps both 102 | // height and width larger than the requested height and width. 103 | while ((halfHeight / options.inSampleSize) > targetHeight 104 | || (halfWidth / options.inSampleSize) > targetWidth) { 105 | options.inSampleSize *= 2; 106 | } 107 | } 108 | options.inJustDecodeBounds = false; 109 | Bitmap bitmap = BitmapFactory.decodeFile(documentId, options); 110 | // Write out the thumbnail to a temporary file 111 | File tempFile = null; 112 | FileOutputStream out = null; 113 | try { 114 | tempFile = File.createTempFile("thumbnail", null, getContext().getCacheDir()); 115 | out = new FileOutputStream(tempFile); 116 | bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); 117 | } catch (IOException e) { 118 | Log.e(LocalStorageProvider.class.getSimpleName(), "Error writing thumbnail", e); 119 | return null; 120 | } finally { 121 | if (out != null) 122 | try { 123 | out.close(); 124 | } catch (IOException e) { 125 | Log.e(LocalStorageProvider.class.getSimpleName(), "Error closing thumbnail", e); 126 | } 127 | } 128 | // It appears the Storage Framework UI caches these results quite 129 | // aggressively so there is little reason to 130 | // write your own caching layer beyond what you need to return a single 131 | // AssetFileDescriptor 132 | return new AssetFileDescriptor(ParcelFileDescriptor.open(tempFile, 133 | ParcelFileDescriptor.MODE_READ_ONLY), 0, 134 | AssetFileDescriptor.UNKNOWN_LENGTH); 135 | } 136 | 137 | @Override 138 | public Cursor queryChildDocuments(final String parentDocumentId, final String[] projection, 139 | final String sortOrder) throws FileNotFoundException { 140 | // Create a cursor with either the requested fields, or the default 141 | // projection if "projection" is null. 142 | final MatrixCursor result = new MatrixCursor(projection != null ? projection 143 | : DEFAULT_DOCUMENT_PROJECTION); 144 | final File parent = new File(parentDocumentId); 145 | for (File file : parent.listFiles()) { 146 | // Don't show hidden files/folders 147 | if (!file.getName().startsWith(".")) { 148 | // Adds the file's display name, MIME type, size, and so on. 149 | includeFile(result, file); 150 | } 151 | } 152 | return result; 153 | } 154 | 155 | @Override 156 | public Cursor queryDocument(final String documentId, final String[] projection) 157 | throws FileNotFoundException { 158 | // Create a cursor with either the requested fields, or the default 159 | // projection if "projection" is null. 160 | final MatrixCursor result = new MatrixCursor(projection != null ? projection 161 | : DEFAULT_DOCUMENT_PROJECTION); 162 | includeFile(result, new File(documentId)); 163 | return result; 164 | } 165 | 166 | private void includeFile(final MatrixCursor result, final File file) 167 | throws FileNotFoundException { 168 | final MatrixCursor.RowBuilder row = result.newRow(); 169 | // These columns are required 170 | row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath()); 171 | row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 172 | String mimeType = getDocumentType(file.getAbsolutePath()); 173 | row.add(Document.COLUMN_MIME_TYPE, mimeType); 174 | int flags = file.canWrite() ? Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE 175 | : 0; 176 | // We only show thumbnails for image files - expect a call to 177 | // openDocumentThumbnail for each file that has 178 | // this flag set 179 | if (mimeType.startsWith("image/")) 180 | flags |= Document.FLAG_SUPPORTS_THUMBNAIL; 181 | row.add(Document.COLUMN_FLAGS, flags); 182 | // COLUMN_SIZE is required, but can be null 183 | row.add(Document.COLUMN_SIZE, file.length()); 184 | // These columns are optional 185 | row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); 186 | // Document.COLUMN_ICON can be a resource id identifying a custom icon. 187 | // The system provides default icons 188 | // based on mime type 189 | // Document.COLUMN_SUMMARY is optional additional information about the 190 | // file 191 | } 192 | 193 | @Override 194 | public String getDocumentType(final String documentId) throws FileNotFoundException { 195 | File file = new File(documentId); 196 | if (file.isDirectory()) 197 | return Document.MIME_TYPE_DIR; 198 | // From FileProvider.getType(Uri) 199 | final int lastDot = file.getName().lastIndexOf('.'); 200 | if (lastDot >= 0) { 201 | final String extension = file.getName().substring(lastDot + 1); 202 | final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 203 | if (mime != null) { 204 | return mime; 205 | } 206 | } 207 | return "application/octet-stream"; 208 | } 209 | 210 | @Override 211 | public void deleteDocument(final String documentId) throws FileNotFoundException { 212 | new File(documentId).delete(); 213 | } 214 | 215 | @Override 216 | public ParcelFileDescriptor openDocument(final String documentId, final String mode, 217 | final CancellationSignal signal) throws FileNotFoundException { 218 | File file = new File(documentId); 219 | final boolean isWrite = (mode.indexOf('w') != -1); 220 | if (isWrite) { 221 | return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); 222 | } else { 223 | return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 224 | } 225 | } 226 | 227 | @Override 228 | public boolean onCreate() { 229 | return true; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/android/filechooser/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/libs/android-support-v4.jar -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-hdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-hdpi/ic_chooser.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-hdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-hdpi/ic_file.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-hdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-hdpi/ic_folder.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-hdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-hdpi/ic_provider.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-mdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-mdpi/ic_chooser.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-mdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-mdpi/ic_file.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-mdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-mdpi/ic_folder.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-mdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-mdpi/ic_provider.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xhdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xhdpi/ic_chooser.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xhdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xhdpi/ic_file.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xhdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xhdpi/ic_folder.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xhdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xhdpi/ic_provider.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xxhdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xxhdpi/ic_chooser.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xxhdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xxhdpi/ic_file.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xxhdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xxhdpi/ic_folder.png -------------------------------------------------------------------------------- /src/android/filechooser/res/drawable-xxhdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdibened/filechooser/36dd9fdecde0a213f089fafa86abc6d257fe6b7c/src/android/filechooser/res/drawable-xxhdpi/ic_provider.png -------------------------------------------------------------------------------- /src/android/filechooser/res/layout/file.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-ca/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Carpeta buida 5 | S\'ha tret o desmuntat l\'emmagatzematge. 6 | Seleccioneu un fitxer 7 | Error en seleccionar el fitxer 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Leerer Ordner 4 | Speicher wurde entferntet. 5 | Wähle eine Datei 6 | Fehler beim Öffnen der Datei 7 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Directorio vacío 5 | Se ha retirado o desmontado el almacenamiento. 6 | Seleccione un archivo 7 | Error al seleccionar el archivo 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dossier vide 5 | Le stockage a été enlevé ou démonté. 6 | Sélectionnez un fichier 7 | Erreur lors de la sélection du fichier 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-ga/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Comhadlann fholamh 5 | Baineadh amach an gléas stórála nó dínascadh é. 6 | Roghnaigh comhad 7 | Tharla botún fad is a bhí comhad á roghnú 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Directory vuota 5 | Lo spazio di archiviazione è stato rimosso o smontato. 6 | Selezionare un file 7 | Errore nel selezionare il File 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 빈 디렉토리 5 | 저장소가 제거되었습니다. 6 | 파일 선택 7 | 파일 선택 오류 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pusty katalog 5 | Pamięć została usunięta lub odmontowana. 6 | Wybierz plik 7 | Błąd, podczas wybierania pliku 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Pasta Vazia 5 | Unidade externa removida ou não preparada. 6 | Selecione um Arquivo 7 | Erro ao selecionar o Arquivo 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Пустая папка 5 | Storage was removed or unmounted. 6 | Выберите файл 7 | Ошибка при выборе файла 8 | 9 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-v11/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Choose a file 19 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values-v19/bool.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values/bool.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | false 6 | 7 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 0dp 19 | 16dp 20 | -------------------------------------------------------------------------------- /src/android/filechooser/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 28 | 29 | -------------------------------------------------------------------------------- /src/android/filechooser/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 | -------------------------------------------------------------------------------- /www/filechooser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * cordova FileChooser plugin 3 | */ 4 | (function(cordova){ 5 | var FileChooser = function() { 6 | 7 | }; 8 | 9 | FileChooser.prototype.open = function(params, success, fail) { 10 | return cordova.exec(function(args) { 11 | success(args); 12 | }, function(args) { 13 | fail(args); 14 | }, 'FileChooser', 'open', [params||{}]); 15 | }; 16 | 17 | window.filechooser = new FileChooser(); 18 | 19 | // backwards compatibility 20 | window.plugins = window.plugins || {}; 21 | window.plugins.filechooser = window.filechooser; 22 | })(window.PhoneGap || window.Cordova || window.cordova); --------------------------------------------------------------------------------