├── .gitignore ├── LICENSE-IANLAKE.txt ├── LICENSE.txt ├── README.markdown ├── aFileChooser ├── .project ├── AndroidManifest.xml ├── libs │ └── android-support-v4.jar ├── proguard.cfg ├── project.properties ├── 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 │ │ ├── strings.xml │ │ └── styles.xml │ └── xml │ │ └── mimetypes.xml └── src │ └── com │ ├── ianhanniballake │ └── localstorage │ │ └── LocalStorageProvider.java │ └── ipaulpro │ └── afilechooser │ ├── FileChooserActivity.java │ ├── FileListAdapter.java │ ├── FileListFragment.java │ ├── FileLoader.java │ └── utils │ └── FileUtils.java ├── aFileChooserExample ├── .project ├── AndroidManifest.xml ├── proguard.cfg ├── project.properties ├── res │ ├── drawable │ │ └── ic_launcher.png │ ├── values-v11 │ │ └── themes.xml │ └── values │ │ ├── strings.xml │ │ └── themes.xml └── src │ └── com │ └── ipaulpro │ └── afilechooserexample │ └── FileChooserExampleActivity.java ├── screenshot-1.png └── screenshot-2.png /.gitignore: -------------------------------------------------------------------------------- 1 | # ndk generated files 2 | **/obj/ 3 | 4 | # built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # lint files 9 | lint.xml 10 | 11 | # files for the dex VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # generated files 18 | bin/ 19 | gen/ 20 | classes/ 21 | gen-external-apklibs/ 22 | 23 | # maven output folder 24 | target 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Eclipse project files 30 | .classpath 31 | .project 32 | .metadata 33 | .settings 34 | 35 | # Proguard folder generated by Eclipse 36 | proguard/ 37 | 38 | # IntelliJ files 39 | *.iml 40 | *.ipr 41 | *.iws 42 | .idea/ 43 | 44 | # OSX files 45 | .DS_Store 46 | 47 | # Windows files 48 | Thumbs.db 49 | 50 | # vi swap files 51 | *.swp 52 | 53 | # backup files 54 | *.bak -------------------------------------------------------------------------------- /LICENSE-IANLAKE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Ian Lake 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | - Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /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 2011 - 2013 Paul Burke 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 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # aFileChooser - Android File Chooser 2 | 3 | aFileChooser is an __Android Library Project__ that simplifies the process of presenting a file chooser on Android 2.1+. 4 | 5 | Intents provide the ability to hook into third-party app components for content selection. This works well for media files, but if you want users to be able to select *any* file, they must have an existing "file explorer" app installed. Because many Android devices don't have stock File Explorers, the developer must often instruct the user to install one, or build one, themselves. aFileChooser solves this issue. 6 | 7 | ### Features: 8 | 9 | * Full file explorer 10 | * Simplify `GET_CONTENT` Intent creation 11 | * Hooks into Storage Access Framework 12 | * Determine MIME data types 13 | * Follows Android conventions (Fragments, Loaders, Intents, etc.) 14 | * Supports API 7+ 15 | 16 | ![screenshot-1](https://raw.github.com/iPaulPro/aFileChooser/master/screenshot-1.png) ![screenshot-2](https://raw.github.com/iPaulPro/aFileChooser/master/screenshot-2.png) 17 | 18 | ## Setup 19 | 20 | Add aFileChooser to your project as an [Android Library Project](http://developer.android.com/guide/developing/projects/projects-eclipse.html#ReferencingLibraryProject). 21 | 22 | Add `FileChooserActivity` to your project's AndroidManifest.xml file with a fully-qualified `name`. The `label` and `icon` set here will be shown in the "Intent Chooser" dialog. 23 | 24 | __Important__ `FileChooserActivity` must have `android:exported="true"` and have the `` set as follows: 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | If you want to use the Storage Access Framework (API 19+), include [Ian Lake](https://github.com/ianhanniballake/)'s `LocalStorageProvider` (included in this library) in your ``: 43 | 44 | 51 | 52 | 53 | 54 | 55 | 56 | __Note__ that like a `ContentProvider`, the `DocumentProvider` `authority` must be unique. You should change `com.ianhanniballake.localstorage.documents` in your Manifest, as well as the `LocalStorageProvider.AUTHORITY` field. 57 | 58 | Using `FileChooserActivity` and `LocalStorageProvider` together are redundant if you're only trying to insure your user has access to local storage. If this is the case, you should enable/disable based on the API level (above: `@bool/use_provider` and `@bool/use_activity`). See the aFileChooserExample project for their values. 59 | 60 | ## Usage 61 | 62 | Use `startActivityForResult(Intent, int)` to launch `FileChooserActivity` directly. `FileChooserActivity` returns the `Uri` of the file selected as the `Intent` data in `onActivityResult(int, int, Intent)`. Alternatively, you can use the helper method `FileUtils.createGetContentIntent()` to construct an `ACTION_GET_CONTENT` Intent that will show an "Intent Chooser" dialog on pre Kit-Kat devices, and the "Documents UI" otherwise. E.g.: 63 | 64 | private static final int REQUEST_CHOOSER = 1234; 65 | 66 | @Override 67 | public void onCreate(Bundle savedInstanceState) { 68 | super.onCreate(savedInstanceState); 69 | 70 | // Create the ACTION_GET_CONTENT Intent 71 | Intent getContentIntent = FileUtils.createGetContentIntent(); 72 | 73 | Intent intent = Intent.createChooser(getContentIntent, "Select a file"); 74 | startActivityForResult(intent, REQUEST_CHOOSER); 75 | } 76 | 77 | @Override 78 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 79 | switch (requestCode) { 80 | case REQUEST_CHOOSER: 81 | if (resultCode == RESULT_OK) { 82 | 83 | final Uri uri = data.getData(); 84 | 85 | // Get the File path from the Uri 86 | String path = FileUtils.getPath(this, uri); 87 | 88 | // Alternatively, use FileUtils.getFile(Context, Uri) 89 | if (path != null && FileUtils.isLocal(path)) { 90 | File file = new File(path); 91 | } 92 | } 93 | break; 94 | } 95 | } 96 | 97 | A more robust example can be found in the aFileChooserExample project. 98 | 99 | __Note__ the `FileUtils` method to get a file path from a `Uri` (`FileUtils.getPath(Context, Uri)`). This works for `File`, `MediaStore`, and `DocumentProvider` `Uris`. 100 | 101 | ## Credits 102 | 103 | Developed by Paul Burke (iPaulPro) - [paulburke.co](http://paulburke.co/) 104 | 105 | Translations by [TomTasche](https://github.com/TomTasche), [booknara](https://github.com/booknara), [brenouchoa](https://github.com/brenouchoa) 106 | 107 | Folder by [Sergio Calcara](http://thenounproject.com/fallacyaccount) from The Noun Project (ic_provider.png) 108 | 109 | Document by [Melvin Salas](http://thenounproject.com/msalas10) from The Noun Project (ic_file.png) 110 | 111 | ## Licenses 112 | 113 | Copyright (C) 2011 - 2013 Paul Burke 114 | 115 | Licensed under the Apache License, Version 2.0 (the "License"); 116 | you may not use this file except in compliance with the License. 117 | You may obtain a copy of the License at 118 | 119 | http://www.apache.org/licenses/LICENSE-2.0 120 | 121 | Unless required by applicable law or agreed to in writing, software 122 | distributed under the License is distributed on an "AS IS" BASIS, 123 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 124 | See the License for the specific language governing permissions and 125 | limitations under the License. 126 | 127 | Portions of FileUtils.java: 128 | 129 | Copyright (C) 2007-2008 OpenIntents.org 130 | 131 | Licensed under the Apache License, Version 2.0 (the "License"); 132 | you may not use this file except in compliance with the License. 133 | You may obtain a copy of the License at 134 | http://www.apache.org/licenses/LICENSE-2.0 135 | 136 | Unless required by applicable law or agreed to in writing, software 137 | distributed under the License is distributed on an "AS IS" BASIS, 138 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139 | See the License for the specific language governing permissions and 140 | limitations under the License. 141 | 142 | LocalStorageProvider.java: 143 | 144 | Copyright (c) 2013, Ian Lake 145 | All rights reserved. 146 | 147 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 148 | 149 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 150 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 151 | - Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 152 | 153 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /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/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /aFileChooser/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/libs/android-support-v4.jar -------------------------------------------------------------------------------- /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/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-19 12 | android.library=true 13 | -------------------------------------------------------------------------------- /aFileChooser/res/drawable-hdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-hdpi/ic_chooser.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-hdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-hdpi/ic_file.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-hdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-hdpi/ic_folder.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-hdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-hdpi/ic_provider.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-mdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-mdpi/ic_chooser.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-mdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-mdpi/ic_file.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-mdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-mdpi/ic_folder.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-mdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-mdpi/ic_provider.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xhdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xhdpi/ic_chooser.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xhdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xhdpi/ic_file.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xhdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xhdpi/ic_folder.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xhdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xhdpi/ic_provider.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xxhdpi/ic_chooser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xxhdpi/ic_chooser.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xxhdpi/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xxhdpi/ic_file.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xxhdpi/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xxhdpi/ic_folder.png -------------------------------------------------------------------------------- /aFileChooser/res/drawable-xxhdpi/ic_provider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/res/drawable-xxhdpi/ic_provider.png -------------------------------------------------------------------------------- /aFileChooser/res/layout/file.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 빈 디렉토리 5 | 저장소가 제거되었습니다. 6 | 파일 선택 7 | 파일 선택 오류 8 | 9 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/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 | -------------------------------------------------------------------------------- /aFileChooser/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Пустая папка 5 | Storage was removed or unmounted. 6 | Выберите файл 7 | Ошибка при выборе файла 8 | 9 | -------------------------------------------------------------------------------- /aFileChooser/res/values-v11/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Choose a file 19 | -------------------------------------------------------------------------------- /aFileChooser/res/values-v19/bool.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | -------------------------------------------------------------------------------- /aFileChooser/res/values/bool.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | false 6 | 7 | -------------------------------------------------------------------------------- /aFileChooser/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 0dp 19 | 16dp 20 | -------------------------------------------------------------------------------- /aFileChooser/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | Empty Directory 19 | Storage was removed or unmounted. 20 | Select a file 21 | Error selecting File 22 | Internal storage 23 | -------------------------------------------------------------------------------- /aFileChooser/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 28 | 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ianhanniballake/localstorage/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 com.ipaulpro.afilechooser.R; 20 | 21 | import java.io.File; 22 | import java.io.FileNotFoundException; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | 26 | public class LocalStorageProvider extends DocumentsProvider { 27 | 28 | public static final String AUTHORITY = "com.ianhanniballake.localstorage.documents"; 29 | 30 | /** 31 | * Default root projection: everything but Root.COLUMN_MIME_TYPES 32 | */ 33 | private final static String[] DEFAULT_ROOT_PROJECTION = new String[] { 34 | Root.COLUMN_ROOT_ID, 35 | Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON, 36 | Root.COLUMN_AVAILABLE_BYTES 37 | }; 38 | /** 39 | * Default document projection: everything but Document.COLUMN_ICON and 40 | * Document.COLUMN_SUMMARY 41 | */ 42 | private final static String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 43 | Document.COLUMN_DOCUMENT_ID, 44 | Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS, Document.COLUMN_MIME_TYPE, 45 | Document.COLUMN_SIZE, 46 | Document.COLUMN_LAST_MODIFIED 47 | }; 48 | 49 | @Override 50 | public Cursor queryRoots(final String[] projection) throws FileNotFoundException { 51 | // Create a cursor with either the requested fields, or the default 52 | // projection if "projection" is null. 53 | final MatrixCursor result = new MatrixCursor(projection != null ? projection 54 | : DEFAULT_ROOT_PROJECTION); 55 | // Add Home directory 56 | File homeDir = Environment.getExternalStorageDirectory(); 57 | final MatrixCursor.RowBuilder row = result.newRow(); 58 | // These columns are required 59 | row.add(Root.COLUMN_ROOT_ID, homeDir.getAbsolutePath()); 60 | row.add(Root.COLUMN_DOCUMENT_ID, homeDir.getAbsolutePath()); 61 | row.add(Root.COLUMN_TITLE, getContext().getString(R.string.internal_storage)); 62 | row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE); 63 | row.add(Root.COLUMN_ICON, R.drawable.ic_provider); 64 | // These columns are optional 65 | row.add(Root.COLUMN_AVAILABLE_BYTES, homeDir.getFreeSpace()); 66 | // Root.COLUMN_MIME_TYPE is another optional column and useful if you 67 | // have multiple roots with different 68 | // types of mime types (roots that don't match the requested mime type 69 | // are automatically hidden) 70 | return result; 71 | } 72 | 73 | @Override 74 | public String createDocument(final String parentDocumentId, final String mimeType, 75 | final String displayName) throws FileNotFoundException { 76 | File newFile = new File(parentDocumentId, displayName); 77 | try { 78 | newFile.createNewFile(); 79 | return newFile.getAbsolutePath(); 80 | } catch (IOException e) { 81 | Log.e(LocalStorageProvider.class.getSimpleName(), "Error creating new file " + newFile); 82 | } 83 | return null; 84 | } 85 | 86 | @Override 87 | public AssetFileDescriptor openDocumentThumbnail(final String documentId, final Point sizeHint, 88 | final CancellationSignal signal) throws FileNotFoundException { 89 | // Assume documentId points to an image file. Build a thumbnail no 90 | // larger than twice the sizeHint 91 | BitmapFactory.Options options = new BitmapFactory.Options(); 92 | options.inJustDecodeBounds = true; 93 | BitmapFactory.decodeFile(documentId, options); 94 | final int targetHeight = 2 * sizeHint.y; 95 | final int targetWidth = 2 * sizeHint.x; 96 | final int height = options.outHeight; 97 | final int width = options.outWidth; 98 | options.inSampleSize = 1; 99 | if (height > targetHeight || width > targetWidth) { 100 | final int halfHeight = height / 2; 101 | final int halfWidth = width / 2; 102 | // Calculate the largest inSampleSize value that is a power of 2 and 103 | // keeps both 104 | // height and width larger than the requested height and width. 105 | while ((halfHeight / options.inSampleSize) > targetHeight 106 | || (halfWidth / options.inSampleSize) > targetWidth) { 107 | options.inSampleSize *= 2; 108 | } 109 | } 110 | options.inJustDecodeBounds = false; 111 | Bitmap bitmap = BitmapFactory.decodeFile(documentId, options); 112 | // Write out the thumbnail to a temporary file 113 | File tempFile = null; 114 | FileOutputStream out = null; 115 | try { 116 | tempFile = File.createTempFile("thumbnail", null, getContext().getCacheDir()); 117 | out = new FileOutputStream(tempFile); 118 | bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); 119 | } catch (IOException e) { 120 | Log.e(LocalStorageProvider.class.getSimpleName(), "Error writing thumbnail", e); 121 | return null; 122 | } finally { 123 | if (out != null) 124 | try { 125 | out.close(); 126 | } catch (IOException e) { 127 | Log.e(LocalStorageProvider.class.getSimpleName(), "Error closing thumbnail", e); 128 | } 129 | } 130 | // It appears the Storage Framework UI caches these results quite 131 | // aggressively so there is little reason to 132 | // write your own caching layer beyond what you need to return a single 133 | // AssetFileDescriptor 134 | return new AssetFileDescriptor(ParcelFileDescriptor.open(tempFile, 135 | ParcelFileDescriptor.MODE_READ_ONLY), 0, 136 | AssetFileDescriptor.UNKNOWN_LENGTH); 137 | } 138 | 139 | @Override 140 | public Cursor queryChildDocuments(final String parentDocumentId, final String[] projection, 141 | final String sortOrder) throws FileNotFoundException { 142 | // Create a cursor with either the requested fields, or the default 143 | // projection if "projection" is null. 144 | final MatrixCursor result = new MatrixCursor(projection != null ? projection 145 | : DEFAULT_DOCUMENT_PROJECTION); 146 | final File parent = new File(parentDocumentId); 147 | for (File file : parent.listFiles()) { 148 | // Don't show hidden files/folders 149 | if (!file.getName().startsWith(".")) { 150 | // Adds the file's display name, MIME type, size, and so on. 151 | includeFile(result, file); 152 | } 153 | } 154 | return result; 155 | } 156 | 157 | @Override 158 | public Cursor queryDocument(final String documentId, final String[] projection) 159 | throws FileNotFoundException { 160 | // Create a cursor with either the requested fields, or the default 161 | // projection if "projection" is null. 162 | final MatrixCursor result = new MatrixCursor(projection != null ? projection 163 | : DEFAULT_DOCUMENT_PROJECTION); 164 | includeFile(result, new File(documentId)); 165 | return result; 166 | } 167 | 168 | private void includeFile(final MatrixCursor result, final File file) 169 | throws FileNotFoundException { 170 | final MatrixCursor.RowBuilder row = result.newRow(); 171 | // These columns are required 172 | row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath()); 173 | row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); 174 | String mimeType = getDocumentType(file.getAbsolutePath()); 175 | row.add(Document.COLUMN_MIME_TYPE, mimeType); 176 | int flags = file.canWrite() ? Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE 177 | : 0; 178 | // We only show thumbnails for image files - expect a call to 179 | // openDocumentThumbnail for each file that has 180 | // this flag set 181 | if (mimeType.startsWith("image/")) 182 | flags |= Document.FLAG_SUPPORTS_THUMBNAIL; 183 | row.add(Document.COLUMN_FLAGS, flags); 184 | // COLUMN_SIZE is required, but can be null 185 | row.add(Document.COLUMN_SIZE, file.length()); 186 | // These columns are optional 187 | row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); 188 | // Document.COLUMN_ICON can be a resource id identifying a custom icon. 189 | // The system provides default icons 190 | // based on mime type 191 | // Document.COLUMN_SUMMARY is optional additional information about the 192 | // file 193 | } 194 | 195 | @Override 196 | public String getDocumentType(final String documentId) throws FileNotFoundException { 197 | File file = new File(documentId); 198 | if (file.isDirectory()) 199 | return Document.MIME_TYPE_DIR; 200 | // From FileProvider.getType(Uri) 201 | final int lastDot = file.getName().lastIndexOf('.'); 202 | if (lastDot >= 0) { 203 | final String extension = file.getName().substring(lastDot + 1); 204 | final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 205 | if (mime != null) { 206 | return mime; 207 | } 208 | } 209 | return "application/octet-stream"; 210 | } 211 | 212 | @Override 213 | public void deleteDocument(final String documentId) throws FileNotFoundException { 214 | new File(documentId).delete(); 215 | } 216 | 217 | @Override 218 | public ParcelFileDescriptor openDocument(final String documentId, final String mode, 219 | final CancellationSignal signal) throws FileNotFoundException { 220 | File file = new File(documentId); 221 | final boolean isWrite = (mode.indexOf('w') != -1); 222 | if (isWrite) { 223 | return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); 224 | } else { 225 | return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 226 | } 227 | } 228 | 229 | @Override 230 | public boolean onCreate() { 231 | return true; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/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 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/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 | } -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/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 | -------------------------------------------------------------------------------- /aFileChooser/src/com/ipaulpro/afilechooser/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 | } -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /aFileChooserExample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 25 | 26 | 27 | 28 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-19 12 | android.library.reference.1=../aFileChooser 13 | -------------------------------------------------------------------------------- /aFileChooserExample/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iPaulPro/aFileChooser/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooserExample/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /aFileChooserExample/res/values-v11/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 |