├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── CODE.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── logos │ ├── CompanionForBand.png │ ├── autoguard.png │ ├── emby.png │ ├── gwhale.png │ ├── instasave.png │ ├── mlmanager.png │ ├── popcorntime.png │ ├── scdl.png │ ├── wallpaperchanger.png │ └── xnretro.png └── screenshots │ ├── chooser-activity.png │ ├── chooser-dialog.png │ ├── create-dialog.png │ └── sample-main.png ├── build.gradle ├── config └── checkstyle │ └── checkstyle.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── library ├── build.gradle ├── gradle.properties └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── net │ │ │ └── rdrei │ │ │ └── android │ │ │ └── dirchooser │ │ │ ├── DirectoryChooserActivity.java │ │ │ ├── DirectoryChooserConfig.java │ │ │ └── DirectoryChooserFragment.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_action_create.png │ │ ├── ic_action_create_light.png │ │ ├── navigation_up.png │ │ └── navigation_up_light.png │ │ ├── drawable-ldpi │ │ ├── navigation_up.png │ │ └── navigation_up_light.png │ │ ├── drawable-mdpi │ │ ├── ic_action_create.png │ │ ├── ic_action_create_light.png │ │ ├── navigation_up.png │ │ └── navigation_up_light.png │ │ ├── drawable-xhdpi │ │ ├── ic_action_create.png │ │ ├── ic_action_create_light.png │ │ ├── navigation_up.png │ │ └── navigation_up_light.png │ │ ├── drawable-xxhdpi │ │ ├── ic_action_create.png │ │ └── ic_action_create_light.png │ │ ├── drawable │ │ └── borderless_button.xml │ │ ├── layout-v11 │ │ └── directory_chooser.xml │ │ ├── layout │ │ ├── dialog_new_folder.xml │ │ ├── directory_chooser.xml │ │ └── directory_chooser_activity.xml │ │ ├── menu │ │ └── directory_chooser.xml │ │ ├── values-ar │ │ └── strings.xml │ │ ├── values-be │ │ └── strings.xml │ │ ├── values-ca │ │ └── strings.xml │ │ ├── values-cs │ │ └── strings.xml │ │ ├── values-da │ │ └── strings.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-el │ │ └── strings.xml │ │ ├── values-es │ │ └── strings.xml │ │ ├── values-et │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-hi │ │ └── strings.xml │ │ ├── values-in │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-ko │ │ └── strings.xml │ │ ├── values-nl │ │ └── strings.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ └── strings.xml │ │ ├── values-ro │ │ └── strings.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-sk │ │ └── strings.xml │ │ ├── values-tr │ │ └── strings.xml │ │ ├── values-v11 │ │ └── colors.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ ├── java │ ├── AndroidManifest.xml │ └── net │ │ └── rdrei │ │ └── android │ │ └── dirchooser │ │ ├── DirectoryChooserActivityTest.java │ │ ├── DirectoryChooserFragmentTest.java │ │ └── TestMenuItem.java │ └── resources │ └── robolectric.properties ├── sample ├── build.gradle ├── contrib │ └── web_icon.png └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── net │ │ └── rdrei │ │ └── android │ │ └── dirchooser │ │ └── sample │ │ ├── DirChooserFragmentSample.java │ │ └── DirChooserSample.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-ldpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── dialog.xml │ └── main.xml │ └── values │ ├── strings.xml │ └── styles.xml └── settings.gradle /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Contributor checklist 2 | 3 | - [ ] I have read the [Contribution Guidelines](https://github.com/passy/Android-DirectoryChooser/CONTRIBUTING.md) 4 | - [ ] My commit messages follow the [Conventional Commit Message Format](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) 5 | - [ ] My contribution is fully baked and ready to be merged as is 6 | 7 | ---------- 8 | 9 | ### Description 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | tmp 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | 11 | #IntelliJ IDEA 12 | .idea 13 | *.iml 14 | classes 15 | 16 | #Maven 17 | target 18 | release.properties 19 | pom.xml.* 20 | 21 | #Command line 22 | local.properties 23 | build.xml 24 | proguard-project.txt 25 | 26 | #Gradle 27 | library/build 28 | sample/build 29 | /build 30 | .gradle 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | android: 7 | components: 8 | - tools 9 | - extra-android-support 10 | - extra-android-m2repository 11 | - build-tools-25.0.2 12 | - android-25 13 | licenses: 14 | - '.+' 15 | 16 | script: 17 | - ./gradlew clean assembleDebug 18 | - ./gradlew testDebug --info 19 | 20 | sudo: false 21 | 22 | cache: 23 | directories: 24 | - $HOME/.gradle 25 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Adam Jorgensen 2 | andres 3 | Artur Dryomov 4 | Avinash R 5 | Bálint Czobor 6 | Guillermo Campelo 7 | j4velin 8 | Jared Burrows 9 | Jocelyn Girard 10 | Klemens Schölhorn 11 | Majeur 12 | MDXDave 13 | Mauro Cardillo 14 | mursts 15 | Pascal Hartig 16 | pyler 17 | Sindre Sorhus 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 3.x (TBD) 5 | ----------------- 6 | 7 | * Add Italian translations (via @maurosoft1973). 8 | 9 | Version 3.2 (2016-02-10) 10 | ------------------------ 11 | 12 | * Re-release because of Maven Central glitch. 13 | * Bumped build tools. 14 | 15 | Version 3.1 (2015-12-22) 16 | ------------------------ 17 | 18 | * Add Estonian translations (via @harri35) 19 | 20 | Version 3.0 (2015-08-01) 21 | ------------------------ 22 | 23 | * Material update 24 | * New Configuration object (breaking change) 25 | * Update to latest SDK version 26 | * Robolectric 3.0 27 | 28 | Version 2.1 (2014-05-18) 29 | ------------------------ 30 | 31 | * Doc fixes 32 | * SDK levle bumps 33 | * Gradle bumps 34 | * Update fixbugs 35 | * Correct icon densities 36 | 37 | Version 2.0 (2014-01-16) 38 | ------------------------ 39 | 40 | * Refactoring into Fragment and Activity 41 | * Sample now shows how to use a DialogFragment 42 | * Better test coverage 43 | * Use Build Tools 19 44 | 45 | Version 1.2 (2013-12-25) 46 | ------------------------ 47 | 48 | * Swedish Translation by @pylerSM 49 | * German Translation by @MDXDave 50 | 51 | Version 1.1 (2013-11-09) 52 | ------------------------ 53 | 54 | * AAR artifact 55 | * Spanish translation by @guicamest 56 | * `EXTRA_INITIAL_DIRECTORY` lets you choose an initial directory by @guicamest 57 | * Japanese translation by @mursts 58 | * 0-pointer fix for ActionBar-free themese by @j4velin 59 | 60 | Version 1.0 (2013-01-16) 61 | ------------------------ 62 | 63 | * Initial Release 64 | -------------------------------------------------------------------------------- /CODE.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | We're happy to accept contributions in the form of new features, bug fixes or 4 | bug reports. If you want to help out, add a comment on the issue you want 5 | to work on and hack away! 6 | 7 | Please note that this project is released with a [Contributor Code of 8 | Conduct](CODE.md). By participating in this project you agree to 9 | abide by its terms. 10 | 11 | ## General Guidelines 12 | 13 | - Please follow the [Conventional Commit Message Guidelines](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#format-of-the-commit-message) 14 | for your commit messages. 15 | - Add tests for new functionality and make sure existing ones don't break when 16 | making changes. 17 | - Add documentation for new functionality. 18 | - Mark breaking changes in your commits. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2013 Pascal Hartig 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android DirectoryChooser 2 | 3 | [![Build Status](http://img.shields.io/travis/passy/Android-DirectoryChooser.svg?style=flat)](https://travis-ci.org/passy/Android-DirectoryChooser) 4 | [![Gittip](http://img.shields.io/gittip/passy.svg?style=flat)](https://www.gittip.com/passy/) 5 | ![Maven Central](https://img.shields.io/maven-central/v/net.rdrei.android.dirchooser/library.svg) 6 | [![Stories in Ready](https://badge.waffle.io/passy/android-directorychooser.svg?label=ready&title=Ready)](http://waffle.io/passy/android-directorychooser) 7 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-DirectoryChooser-blue.svg?style=flat)](http://android-arsenal.com/details/1/159) 8 | 9 | A simple directory chooser you can integrate into your Android app. 10 | 11 | This version of the library has no additional dependencies, but requires Android 12 | v11+ to work. There is, however, [a pre-v11-branch][3] that supports down to v7 13 | using ActionBarSherlock. 14 | 15 | You can download the sample app from the Play Store: 16 | 17 | Get it on Google Play 18 | 19 | Based on the DirectoryChooser from the excellent 20 | [AntennaPod App](https://github.com/danieloeh/AntennaPod) by danieloeh. 21 | 22 | 23 | 24 | 25 | 26 | 27 |
As stand-alone Activity
As DialogFragment
28 | 29 | ## Usage 30 | 31 | For a full example see the `sample` app in the 32 | [repository](https://github.com/passy/Android-DirectoryChooser/tree/master/sample). 33 | 34 | ### From Maven Central 35 | 36 | Library releases are available on Maven Central, snapshots can be retrieved 37 | from Sonatype: 38 | 39 | *Release (SDK 11+)* 40 | 41 | **Gradle** 42 | 43 | ```groovy 44 | compile 'net.rdrei.android.dirchooser:library:3.2@aar' 45 | ``` 46 | 47 | **Maven** 48 | 49 | ```xml 50 | 51 | net.rdrei.android.dirchooser 52 | library 53 | 3.2 54 | aar 55 | 56 | ``` 57 | 58 | *Release (SDK 7+)* 59 | 60 | **Gradle** 61 | 62 | ```groovy 63 | compile 'net.rdrei.android.dirchooser:library:1.0-pre-v11@aar' 64 | ``` 65 | 66 | **Maven** 67 | 68 | ```xml 69 | 70 | net.rdrei.android.dirchooser 71 | library 72 | 1.0-pre-v11 73 | aar 74 | 75 | ``` 76 | 77 | *Snapshot (SDK 11+)* 78 | 79 | ```groovy 80 | compile 'net.rdrei.android.dirchooser:library:3.2-SNAPSHOT@aar' 81 | ``` 82 | 83 | *Snapshot (SDK 4+)* 84 | 85 | ```groovy 86 | compile 'net.rdrei.android.dirchooser:library:2.0-pre-v11-SNAPSHOT@aar' 87 | ``` 88 | 89 | ### As Library Project 90 | 91 | Alternatively, check out this repository and add it as a library project. 92 | 93 | ```console 94 | $ git clone https://github.com/passy/Android-DirectoryChooser.git 95 | ``` 96 | 97 | Import the project into your favorite IDE and add 98 | `android.library.reference.1=/path/to/Android-DirectoryChooser/library` to your 99 | `project.properties`. 100 | 101 | ### Manifest 102 | 103 | You need to declare the `DirectoryChooserActivity` and request the 104 | `android.permission.WRITE_EXTERNAL_STORAGE` permission. 105 | 106 | ```xml 107 | 108 | ... 109 | 110 | 111 | 112 | ``` 113 | 114 | ### Activity 115 | 116 | To choose a directory, start the activity from your app logic: 117 | 118 | ```java 119 | 120 | final Intent chooserIntent = new Intent(this, DirectoryChooserActivity.class); 121 | 122 | final DirectoryChooserConfig config = DirectoryChooserConfig.builder() 123 | .newDirectoryName("DirChooserSample") 124 | .allowReadOnlyDirectory(true) 125 | .allowNewDirectoryNameModification(true) 126 | .build(); 127 | 128 | chooserIntent.putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config); 129 | 130 | // REQUEST_DIRECTORY is a constant integer to identify the request, e.g. 0 131 | startActivityForResult(chooserIntent, REQUEST_DIRECTORY); 132 | ``` 133 | 134 | Handle the result in your `onActivityResult` method: 135 | 136 | ```java 137 | @Override 138 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 139 | super.onActivityResult(requestCode, resultCode, data); 140 | 141 | if (requestCode == REQUEST_DIRECTORY) { 142 | if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { 143 | handleDirectoryChoice(data 144 | .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)); 145 | } else { 146 | // Nothing selected 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | ### Fragment 153 | 154 | You can also use the underlying DialogFragment for even better integration. 155 | Whether you use the Fragment as a Dialog or not is completely up to you. All you 156 | have to do is implement the `OnFragmentInteractionListener` interface to respond 157 | to the events that a directory is selected or the user cancels the dialog: 158 | 159 | ```java 160 | public class DirChooserFragmentSample extends Activity implements 161 | DirectoryChooserFragment.OnFragmentInteractionListener { 162 | 163 | private TextView mDirectoryTextView; 164 | private DirectoryChooserFragment mDialog; 165 | 166 | @Override 167 | protected void onCreate(Bundle savedInstanceState) { 168 | super.onCreate(savedInstanceState); 169 | setContentView(R.layout.dialog); 170 | final DirectoryChooserConfig config = DirectoryChooserConfig.builder() 171 | .newDirectoryName("DialogSample") 172 | .build(); 173 | mDialog = DirectoryChooserFragment.newInstance(config); 174 | 175 | mDirectoryTextView = (TextView) findViewById(R.id.textDirectory); 176 | 177 | findViewById(R.id.btnChoose) 178 | .setOnClickListener(new View.OnClickListener() { 179 | @Override 180 | public void onClick(View v) { 181 | mDialog.show(getFragmentManager(), null); 182 | } 183 | }); 184 | } 185 | 186 | @Override 187 | public void onSelectDirectory(@NonNull String path) { 188 | mDirectoryTextView.setText(path); 189 | mDialog.dismiss(); 190 | } 191 | 192 | @Override 193 | public void onCancelChooser() { 194 | mDialog.dismiss(); 195 | } 196 | } 197 | 198 | ``` 199 | 200 | If calling the directory chooser from your own fragment, be sure to set the target fragment first: 201 | 202 | ```java 203 | @Override 204 | public void onClick(View v) { 205 | mDialog.setTargetFragment(this, 0); 206 | mDialog.show(getFragmentManager(), null); 207 | } 208 | ``` 209 | 210 | ### Configuration 211 | 212 | The Directory Chooser is configured through a parcelable configuration object, which is great 213 | because it means that you don't have to tear your hair out over finicky string extras. Instead, 214 | you get auto-completion and a nice immutable data structure. Here's what you can configure: 215 | 216 | #### `newDirectoryName` : String (required) 217 | 218 | Name of the directory to create. User can change this name when he creates the 219 | folder. To avoid this use `allowNewDirectoryNameModification` argument. 220 | 221 | #### `initialDirectory` : String (default: "") 222 | 223 | Optional argument to define the path of the directory that will be shown first. 224 | If it is not sent or if path denotes a non readable/writable directory or it is not a directory, 225 | it defaults to `android.os.Environment#getExternalStorageDirectory()`. 226 | 227 | #### `allowReadOnlyDirectory` : Boolean (default: false) 228 | 229 | Argument to define whether or not the directory chooser allows read-only paths to be chosen. If it 230 | false only directories with read-write access can be chosen. 231 | 232 | #### `allowNewDirectoryNameModification` : Boolean (default: false) 233 | 234 | Argument to define whether or not the directory chooser allows modification of provided new 235 | directory name. 236 | 237 | #### Example 238 | 239 | ```java 240 | final DirectoryChooserConfig config = DirectoryChooserConfig.builder() 241 | .newDirectoryName("DialogSample") 242 | .allowNewDirectoryNameModification(true) 243 | .allowReadOnlyDirectory(true) 244 | .initialDirectory("/sdcard") 245 | .build(); 246 | ``` 247 | 248 | ## Apps using this 249 | 250 | 251 | 252 | 260 | 268 | 276 | 284 | 292 | 300 | 301 | 302 | 310 | 318 | 326 | 334 | 342 | 343 |
253 | 254 | Popcorn Time for Android 257 |
Popcorn Time for Android
258 |
259 |
261 | 262 | Emby for Android 265 |
Emby for Android
266 |
267 |
269 | 270 | Downloader for SoundCloud 273 |
Downloader for SoundCloud
274 |
275 |
277 | 278 | Wallpaper Changer 281 |
Wallpaper Changer
282 |
283 |
285 | 286 | XnRetro 289 |
XnRetro
290 |
291 |
293 | 294 | InstaSave - Instagram Save 297 |
InstaSave
298 |
299 |
303 | 304 | ML Manager - App manager 307 |
ML Manager
308 |
309 |
311 | 312 | AutoGuard - Dash Cam 315 |
AutoGuard
316 |
317 |
319 | 320 | Companion for Band 323 |
Companion for Band
324 |
325 |
327 | 328 | Swallow Server 331 |
Swallow Server
332 |
333 |
335 | 336 | Photo Map 339 |
Photo Map
340 |
341 |
344 | 345 | To add your own app, please send a pull request. 346 | 347 | ## Releasing 348 | 349 | *Preparation* 350 | 351 | To release a new snapshot on Maven Central, add your credentials to 352 | `~/.gradle/gradle.properties` (you get them from http://oss.sonatype.org) as 353 | well as your signing GPG key: 354 | 355 | ``` 356 | signing.keyId=0x18EEA4AF 357 | signing.secretKeyRingFile=/home/pascal/.gnupg/secring.gpg 358 | 359 | NEXUS_USERNAME=username 360 | NEXUS_PASSWORD=password 361 | ``` 362 | 363 | *Staging* 364 | 365 | To upload a new snapshot, just run gradle's `uploadArchives` command: 366 | 367 | ``` 368 | gradle :library:uploadArchives 369 | ``` 370 | 371 | *Release* 372 | 373 | Update versions and remove `-SNAPSHOT` suffixes. 374 | 375 | ``` 376 | gradle build :library:uploadArchives 377 | ``` 378 | 379 | ## License 380 | 381 | ```text 382 | Copyright 2013-2016 Pascal Hartig 383 | 384 | Licensed under the Apache License, Version 2.0 (the "License"); 385 | you may not use this file except in compliance with the License. 386 | You may obtain a copy of the License at 387 | 388 | http://www.apache.org/licenses/LICENSE-2.0 389 | 390 | Unless required by applicable law or agreed to in writing, software 391 | distributed under the License is distributed on an "AS IS" BASIS, 392 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 393 | See the License for the specific language governing permissions and 394 | limitations under the License. 395 | ``` 396 | 397 | ## Thanks 398 | 399 | Sample App icon by [Frank Souza](http://franksouza183.deviantart.com/). 400 | 401 | [3]: https://github.com/passy/Android-DirectoryChooser/tree/pre-v11 402 | [4]: https://play.google.com/store/apps/details?id=net.rdrei.android.dirchooser.sample 403 | -------------------------------------------------------------------------------- /assets/logos/CompanionForBand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/CompanionForBand.png -------------------------------------------------------------------------------- /assets/logos/autoguard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/autoguard.png -------------------------------------------------------------------------------- /assets/logos/emby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/emby.png -------------------------------------------------------------------------------- /assets/logos/gwhale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/gwhale.png -------------------------------------------------------------------------------- /assets/logos/instasave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/instasave.png -------------------------------------------------------------------------------- /assets/logos/mlmanager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/mlmanager.png -------------------------------------------------------------------------------- /assets/logos/popcorntime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/popcorntime.png -------------------------------------------------------------------------------- /assets/logos/scdl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/scdl.png -------------------------------------------------------------------------------- /assets/logos/wallpaperchanger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/wallpaperchanger.png -------------------------------------------------------------------------------- /assets/logos/xnretro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/logos/xnretro.png -------------------------------------------------------------------------------- /assets/screenshots/chooser-activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/screenshots/chooser-activity.png -------------------------------------------------------------------------------- /assets/screenshots/chooser-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/screenshots/chooser-dialog.png -------------------------------------------------------------------------------- /assets/screenshots/create-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/screenshots/create-dialog.png -------------------------------------------------------------------------------- /assets/screenshots/sample-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/assets/screenshots/sample-main.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | jcenter() 5 | maven { 6 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 7 | } 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:2.3.0' 12 | } 13 | } 14 | 15 | subprojects { 16 | repositories { 17 | mavenCentral() 18 | maven { 19 | url 'https://jitpack.io/' 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=3.2 2 | GROUP=net.rdrei.android.dirchooser 3 | 4 | POM_DESCRIPTION=A directory chooser library for Android 5 | POM_URL=https://github.com/passy/Android-DirectoryChooser 6 | POM_SCM_URL=https://github.com/passy/Android-DirectoryChooser 7 | POM_SCM_CONNECTION=scm:git@github.com:passy/Android-DirectoryChooser.git 8 | POM_SCM_DEV_CONNECTION=scm:git@github.com:passy/Android-DirectoryChooser.git 9 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 10 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 11 | POM_LICENCE_DIST=repo 12 | POM_DEVELOPER_ID=passy 13 | POM_DEVELOPER_NAME=Pascal Hartig 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jul 27 12:11:54 BST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-3.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'checkstyle' 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.11.3' 11 | } 12 | } 13 | 14 | dependencies { 15 | compile 'com.android.support:appcompat-v7:25.3.1' 16 | compile 'com.android.support:support-annotations:25.3.1' 17 | compile 'com.github.guardian:Option:e933c3a31d' 18 | compile 'com.github.frankiesardo:auto-parcel:0.3.1' 19 | compile 'com.google.auto.value:auto-value:1.1' 20 | annotationProcessor 'com.github.frankiesardo:auto-parcel-processor:0.3.1' 21 | 22 | testCompile 'junit:junit:4.12' 23 | testCompile 'org.mockito:mockito-all:1.9.5' 24 | testCompile 'com.squareup.assertj:assertj-android:1.1.1' 25 | testCompile 'org.apache.maven:maven-ant-tasks:2.1.3' // fixes issue on linux/mac 26 | testCompile 'org.robolectric:robolectric:3.3' 27 | testCompile 'org.robolectric:shadows-support-v4:3.3' 28 | } 29 | 30 | android { 31 | compileSdkVersion 25 32 | buildToolsVersion '25.0.2' 33 | 34 | defaultConfig { 35 | minSdkVersion 11 36 | targetSdkVersion 22 37 | } 38 | 39 | lintOptions { 40 | abortOnError false 41 | } 42 | 43 | compileOptions { 44 | sourceCompatibility JavaVersion.VERSION_1_7 45 | targetCompatibility JavaVersion.VERSION_1_7 46 | } 47 | } 48 | 49 | checkstyle { 50 | configFile project.file('../config/checkstyle/checkstyle.xml') 51 | showViolations true 52 | } 53 | 54 | apply from: 'https://raw.githubusercontent.com/robbypond/gradle-mvn-push/8f35c0555e9c71154c08dfde9397340099f8dc9f/gradle-mvn-push.gradle' 55 | 56 | gradle.taskGraph.whenReady { taskGraph -> 57 | if (taskGraph.allTasks.any { it instanceof Sign } && 58 | ':library:uploadArchives' in gradle.startParameter.taskNames) { 59 | // Use Java 6's console to read from the console (no good for a CI environment) 60 | Console console = System.console() 61 | console.printf "\n\nWe have to sign some things in this build.\n\nPlease enter your signing details.\n\n" 62 | allprojects { ext."signing.password" = console.readPassword("PGP Private Key Password: ") } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Android-DirectoryChooser Library 2 | POM_ARTIFACT_ID=library 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/java/net/rdrei/android/dirchooser/DirectoryChooserActivity.java: -------------------------------------------------------------------------------- 1 | package net.rdrei.android.dirchooser; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.ActionBar; 5 | import android.app.FragmentManager; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.view.MenuItem; 12 | 13 | /** 14 | * Let's the user choose a directory on the storage device. The selected folder 15 | * will be sent back to the starting activity as an activity result. 16 | */ 17 | public class DirectoryChooserActivity extends AppCompatActivity implements 18 | DirectoryChooserFragment.OnFragmentInteractionListener { 19 | public static final String EXTRA_CONFIG = "config"; 20 | public static final String RESULT_SELECTED_DIR = "selected_dir"; 21 | public static final int RESULT_CODE_DIR_SELECTED = 1; 22 | 23 | @Override 24 | public void onCreate(@Nullable Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setupActionBar(); 27 | setContentView(R.layout.directory_chooser_activity); 28 | 29 | final DirectoryChooserConfig config = getIntent().getParcelableExtra(EXTRA_CONFIG); 30 | 31 | if (config == null) { 32 | throw new IllegalArgumentException( 33 | "You must provide EXTRA_CONFIG when starting the DirectoryChooserActivity."); 34 | } 35 | 36 | if (savedInstanceState == null) { 37 | final FragmentManager fragmentManager = getFragmentManager(); 38 | final DirectoryChooserFragment fragment = DirectoryChooserFragment.newInstance(config); 39 | fragmentManager.beginTransaction() 40 | .add(R.id.main, fragment) 41 | .commit(); 42 | } 43 | } 44 | 45 | /* package */void setupActionBar() { 46 | // there might not be an ActionBar, for example when started in Theme.Holo.Dialog.NoActionBar theme 47 | @SuppressLint("AppCompatMethod") final ActionBar actionBar = getActionBar(); 48 | if (actionBar != null) { 49 | actionBar.setDisplayHomeAsUpEnabled(true); 50 | } 51 | } 52 | 53 | @Override 54 | public boolean onOptionsItemSelected(MenuItem item) { 55 | final int itemId = item.getItemId(); 56 | 57 | if (itemId == android.R.id.home) { 58 | setResult(RESULT_CANCELED); 59 | finish(); 60 | 61 | return true; 62 | } 63 | 64 | return super.onOptionsItemSelected(item); 65 | } 66 | 67 | @Override 68 | public void onSelectDirectory(@NonNull String path) { 69 | final Intent intent = new Intent(); 70 | intent.putExtra(RESULT_SELECTED_DIR, path); 71 | setResult(RESULT_CODE_DIR_SELECTED, intent); 72 | finish(); 73 | } 74 | 75 | @Override 76 | public void onCancelChooser() { 77 | setResult(RESULT_CANCELED); 78 | finish(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/java/net/rdrei/android/dirchooser/DirectoryChooserConfig.java: -------------------------------------------------------------------------------- 1 | package net.rdrei.android.dirchooser; 2 | 3 | import android.os.Parcelable; 4 | 5 | import auto.parcel.AutoParcel; 6 | 7 | @AutoParcel 8 | public abstract class DirectoryChooserConfig implements Parcelable { 9 | /** 10 | * @return Builder for a new DirectoryChooserConfig. 11 | */ 12 | public static Builder builder() { 13 | return new AutoParcel_DirectoryChooserConfig.Builder() 14 | .initialDirectory("") 15 | .allowNewDirectoryNameModification(false) 16 | .allowReadOnlyDirectory(false); 17 | } 18 | 19 | /** 20 | * Name of the directory to create. User can change this name when he creates the 21 | * folder. To avoid this use {@link #allowNewDirectoryNameModification} argument. 22 | */ 23 | abstract String newDirectoryName(); 24 | 25 | /** 26 | * Optional argument to define the path of the directory 27 | * that will be shown first. 28 | * If it is not sent or if path denotes a non readable/writable directory 29 | * or it is not a directory, it defaults to 30 | * {@link android.os.Environment#getExternalStorageDirectory()} 31 | */ 32 | abstract String initialDirectory(); 33 | 34 | /** 35 | * Argument to define whether or not the directory chooser 36 | * allows read-only paths to be chosen. If it false only 37 | * directories with read-write access can be chosen. 38 | */ 39 | abstract boolean allowReadOnlyDirectory(); 40 | 41 | 42 | /** 43 | * Argument to define whether or not the directory chooser 44 | * allows modification of provided new directory name. 45 | */ 46 | abstract boolean allowNewDirectoryNameModification(); 47 | 48 | @AutoParcel.Builder 49 | public abstract static class Builder { 50 | public abstract Builder newDirectoryName(String s); 51 | public abstract Builder initialDirectory(String s); 52 | public abstract Builder allowReadOnlyDirectory(boolean b); 53 | public abstract Builder allowNewDirectoryNameModification(boolean b); 54 | public abstract DirectoryChooserConfig build(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/net/rdrei/android/dirchooser/DirectoryChooserFragment.java: -------------------------------------------------------------------------------- 1 | package net.rdrei.android.dirchooser; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.app.AlertDialog; 6 | import android.app.DialogFragment; 7 | import android.app.Fragment; 8 | import android.content.DialogInterface; 9 | import android.content.res.Resources; 10 | import android.content.res.TypedArray; 11 | import android.graphics.Color; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.os.FileObserver; 15 | import android.support.annotation.NonNull; 16 | import android.support.annotation.Nullable; 17 | import android.text.Editable; 18 | import android.text.TextUtils; 19 | import android.text.TextWatcher; 20 | import android.util.Log; 21 | import android.view.LayoutInflater; 22 | import android.view.Menu; 23 | import android.view.MenuInflater; 24 | import android.view.MenuItem; 25 | import android.view.View; 26 | import android.view.View.OnClickListener; 27 | import android.view.ViewGroup; 28 | import android.widget.AdapterView; 29 | import android.widget.AdapterView.OnItemClickListener; 30 | import android.widget.ArrayAdapter; 31 | import android.widget.Button; 32 | import android.widget.EditText; 33 | import android.widget.ImageButton; 34 | import android.widget.ListView; 35 | import android.widget.TextView; 36 | import android.widget.Toast; 37 | 38 | import com.gu.option.Option; 39 | import com.gu.option.UnitFunction; 40 | 41 | import java.io.File; 42 | import java.util.ArrayList; 43 | import java.util.Arrays; 44 | import java.util.Collections; 45 | import java.util.List; 46 | 47 | /** 48 | * Activities that contain this fragment must implement the 49 | * {@link OnFragmentInteractionListener} interface 50 | * to handle interaction events. 51 | * Use the {@link DirectoryChooserFragment#newInstance} factory method to 52 | * create an instance of this fragment. 53 | */ 54 | public class DirectoryChooserFragment extends DialogFragment { 55 | public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY"; 56 | private static final String ARG_CONFIG = "CONFIG"; 57 | private static final String TAG = DirectoryChooserFragment.class.getSimpleName(); 58 | private String mNewDirectoryName; 59 | private String mInitialDirectory; 60 | 61 | private Option mListener = Option.none(); 62 | 63 | private Button mBtnConfirm; 64 | private Button mBtnCancel; 65 | private ImageButton mBtnNavUp; 66 | private ImageButton mBtnCreateFolder; 67 | private TextView mTxtvSelectedFolder; 68 | private ListView mListDirectories; 69 | 70 | private ArrayAdapter mListDirectoriesAdapter; 71 | private List mFilenames; 72 | /** 73 | * The directory that is currently being shown. 74 | */ 75 | private File mSelectedDir; 76 | private File[] mFilesInDir; 77 | private FileObserver mFileObserver; 78 | private DirectoryChooserConfig mConfig; 79 | 80 | public DirectoryChooserFragment() { 81 | // Required empty public constructor 82 | } 83 | 84 | /** 85 | * To create the config, make use of the provided 86 | * {@link DirectoryChooserConfig#builder()}. 87 | * 88 | * @return A new instance of DirectoryChooserFragment. 89 | */ 90 | public static DirectoryChooserFragment newInstance(@NonNull final DirectoryChooserConfig config) { 91 | final DirectoryChooserFragment fragment = new DirectoryChooserFragment(); 92 | final Bundle args = new Bundle(); 93 | args.putParcelable(ARG_CONFIG, config); 94 | fragment.setArguments(args); 95 | return fragment; 96 | } 97 | 98 | @Override 99 | public void onSaveInstanceState(@NonNull final Bundle outState) { 100 | super.onSaveInstanceState(outState); 101 | 102 | if (mSelectedDir != null) { 103 | outState.putString(KEY_CURRENT_DIRECTORY, mSelectedDir.getAbsolutePath()); 104 | } 105 | } 106 | 107 | @Override 108 | public void onCreate(final Bundle savedInstanceState) { 109 | super.onCreate(savedInstanceState); 110 | 111 | if (getArguments() == null) { 112 | throw new IllegalArgumentException( 113 | "You must create DirectoryChooserFragment via newInstance()."); 114 | } 115 | mConfig = getArguments().getParcelable(ARG_CONFIG); 116 | 117 | if (mConfig == null) { 118 | throw new NullPointerException("No ARG_CONFIG provided for DirectoryChooserFragment " + 119 | "creation."); 120 | } 121 | 122 | mNewDirectoryName = mConfig.newDirectoryName(); 123 | mInitialDirectory = mConfig.initialDirectory(); 124 | 125 | if (savedInstanceState != null) { 126 | mInitialDirectory = savedInstanceState.getString(KEY_CURRENT_DIRECTORY); 127 | } 128 | 129 | if (getShowsDialog()) { 130 | setStyle(DialogFragment.STYLE_NO_TITLE, 0); 131 | } else { 132 | setHasOptionsMenu(true); 133 | } 134 | 135 | if (!mConfig.allowNewDirectoryNameModification() && TextUtils.isEmpty(mNewDirectoryName)) { 136 | throw new IllegalArgumentException("New directory name must have a strictly positive " + 137 | "length (not zero) when user is not allowed to modify it."); 138 | } 139 | } 140 | 141 | @Override 142 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container, 143 | final Bundle savedInstanceState) { 144 | 145 | assert getActivity() != null; 146 | final View view = inflater.inflate(R.layout.directory_chooser, container, false); 147 | 148 | mBtnConfirm = (Button) view.findViewById(R.id.btnConfirm); 149 | mBtnCancel = (Button) view.findViewById(R.id.btnCancel); 150 | mBtnNavUp = (ImageButton) view.findViewById(R.id.btnNavUp); 151 | mBtnCreateFolder = (ImageButton) view.findViewById(R.id.btnCreateFolder); 152 | mTxtvSelectedFolder = (TextView) view.findViewById(R.id.txtvSelectedFolder); 153 | mListDirectories = (ListView) view.findViewById(R.id.directoryList); 154 | 155 | mBtnConfirm.setOnClickListener(new OnClickListener() { 156 | 157 | @Override 158 | public void onClick(final View v) { 159 | if (isValidFile(mSelectedDir)) { 160 | returnSelectedFolder(); 161 | } 162 | } 163 | }); 164 | 165 | mBtnCancel.setOnClickListener(new OnClickListener() { 166 | 167 | @Override 168 | public void onClick(final View v) { 169 | mListener.foreach(new UnitFunction() { 170 | @Override 171 | public void apply(final OnFragmentInteractionListener listener) { 172 | listener.onCancelChooser(); 173 | } 174 | }); 175 | } 176 | }); 177 | 178 | mListDirectories.setOnItemClickListener(new OnItemClickListener() { 179 | 180 | @Override 181 | public void onItemClick(final AdapterView parent, final View view, 182 | final int position, final long id) { 183 | debug("Selected index: %d", position); 184 | if (mFilesInDir != null && position >= 0 185 | && position < mFilesInDir.length) { 186 | changeDirectory(mFilesInDir[position]); 187 | } 188 | } 189 | }); 190 | 191 | mBtnNavUp.setOnClickListener(new OnClickListener() { 192 | 193 | @Override 194 | public void onClick(final View v) { 195 | final File parent; 196 | if (mSelectedDir != null 197 | && (parent = mSelectedDir.getParentFile()) != null) { 198 | changeDirectory(parent); 199 | } 200 | } 201 | }); 202 | 203 | mBtnCreateFolder.setOnClickListener(new OnClickListener() { 204 | @Override 205 | public void onClick(final View v) { 206 | openNewFolderDialog(); 207 | } 208 | }); 209 | 210 | if (!getShowsDialog()) { 211 | mBtnCreateFolder.setVisibility(View.GONE); 212 | } 213 | 214 | adjustResourceLightness(); 215 | 216 | mFilenames = new ArrayList<>(); 217 | mListDirectoriesAdapter = new ArrayAdapter<>(getActivity(), 218 | android.R.layout.simple_list_item_1, mFilenames); 219 | mListDirectories.setAdapter(mListDirectoriesAdapter); 220 | 221 | final File initialDir; 222 | if (!TextUtils.isEmpty(mInitialDirectory) && isValidFile(new File(mInitialDirectory))) { 223 | initialDir = new File(mInitialDirectory); 224 | } else { 225 | initialDir = Environment.getExternalStorageDirectory(); 226 | } 227 | 228 | changeDirectory(initialDir); 229 | 230 | return view; 231 | } 232 | 233 | private void adjustResourceLightness() { 234 | // change up button to light version if using dark theme 235 | int color = 0xFFFFFF; 236 | final Resources.Theme theme = getActivity().getTheme(); 237 | 238 | if (theme != null) { 239 | final TypedArray backgroundAttributes = theme.obtainStyledAttributes( 240 | new int[]{android.R.attr.colorBackground}); 241 | 242 | if (backgroundAttributes != null) { 243 | color = backgroundAttributes.getColor(0, 0xFFFFFF); 244 | backgroundAttributes.recycle(); 245 | } 246 | } 247 | 248 | // convert to greyscale and check if < 128 249 | if (color != 0xFFFFFF && 0.21 * Color.red(color) + 250 | 0.72 * Color.green(color) + 251 | 0.07 * Color.blue(color) < 128) { 252 | mBtnNavUp.setImageResource(R.drawable.navigation_up_light); 253 | mBtnCreateFolder.setImageResource(R.drawable.ic_action_create_light); 254 | } 255 | } 256 | 257 | @Override 258 | public void onAttach(final Activity activity) { 259 | super.onAttach(activity); 260 | 261 | if (activity instanceof OnFragmentInteractionListener) { 262 | mListener = Option.some((OnFragmentInteractionListener) activity); 263 | } else { 264 | Fragment owner = getTargetFragment(); 265 | if (owner instanceof OnFragmentInteractionListener) { 266 | mListener = Option.some((OnFragmentInteractionListener) owner); 267 | } 268 | } 269 | } 270 | 271 | @Override 272 | public void onDetach() { 273 | super.onDetach(); 274 | mListener = null; 275 | } 276 | 277 | @Override 278 | public void onPause() { 279 | super.onPause(); 280 | if (mFileObserver != null) { 281 | mFileObserver.stopWatching(); 282 | } 283 | } 284 | 285 | @Override 286 | public void onResume() { 287 | super.onResume(); 288 | if (mFileObserver != null) { 289 | mFileObserver.startWatching(); 290 | } 291 | } 292 | 293 | @Override 294 | public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 295 | inflater.inflate(R.menu.directory_chooser, menu); 296 | 297 | final MenuItem menuItem = menu.findItem(R.id.new_folder_item); 298 | 299 | if (menuItem == null) { 300 | return; 301 | } 302 | 303 | menuItem.setVisible(isValidFile(mSelectedDir) && mNewDirectoryName != null); 304 | } 305 | 306 | @Override 307 | public boolean onOptionsItemSelected(final MenuItem item) { 308 | final int itemId = item.getItemId(); 309 | 310 | if (itemId == R.id.new_folder_item) { 311 | openNewFolderDialog(); 312 | return true; 313 | } 314 | 315 | return super.onOptionsItemSelected(item); 316 | } 317 | 318 | /** 319 | * Shows a confirmation dialog that asks the user if he wants to create a 320 | * new folder. User can modify provided name, if it was not disallowed. 321 | */ 322 | private void openNewFolderDialog() { 323 | @SuppressLint("InflateParams") 324 | final View dialogView = getActivity().getLayoutInflater().inflate( 325 | R.layout.dialog_new_folder, null); 326 | final TextView msgView = (TextView) dialogView.findViewById(R.id.msgText); 327 | final EditText editText = (EditText) dialogView.findViewById(R.id.editText); 328 | editText.setText(mNewDirectoryName); 329 | msgView.setText(getString(R.string.create_folder_msg, mNewDirectoryName)); 330 | 331 | final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) 332 | .setTitle(R.string.create_folder_label) 333 | .setView(dialogView) 334 | .setNegativeButton(R.string.cancel_label, 335 | new DialogInterface.OnClickListener() { 336 | 337 | @Override 338 | public void onClick(final DialogInterface dialog, final int which) { 339 | dialog.dismiss(); 340 | } 341 | }) 342 | .setPositiveButton(R.string.confirm_label, 343 | new DialogInterface.OnClickListener() { 344 | 345 | @Override 346 | public void onClick(final DialogInterface dialog, final int which) { 347 | dialog.dismiss(); 348 | mNewDirectoryName = editText.getText().toString(); 349 | final int msg = createFolder(); 350 | Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show(); 351 | } 352 | }) 353 | .show(); 354 | 355 | alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(editText.getText().length() != 0); 356 | 357 | editText.addTextChangedListener(new TextWatcher() { 358 | @Override 359 | public void beforeTextChanged(final CharSequence charSequence, final int i, final int i2, final int i3) { 360 | 361 | } 362 | 363 | @Override 364 | public void onTextChanged(final CharSequence charSequence, final int i, final int i2, final int i3) { 365 | final boolean textNotEmpty = charSequence.length() != 0; 366 | alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(textNotEmpty); 367 | msgView.setText(getString(R.string.create_folder_msg, charSequence.toString())); 368 | } 369 | 370 | @Override 371 | public void afterTextChanged(final Editable editable) { 372 | 373 | } 374 | }); 375 | 376 | editText.setVisibility(mConfig.allowNewDirectoryNameModification() 377 | ? View.VISIBLE : View.GONE); 378 | } 379 | 380 | private static void debug(final String message, final Object... args) { 381 | Log.d(TAG, String.format(message, args)); 382 | } 383 | 384 | /** 385 | * Change the directory that is currently being displayed. 386 | * 387 | * @param dir The file the activity should switch to. This File must be 388 | * non-null and a directory, otherwise the displayed directory 389 | * will not be changed 390 | */ 391 | private void changeDirectory(final File dir) { 392 | if (dir == null) { 393 | debug("Could not change folder: dir was null"); 394 | } else if (!dir.isDirectory()) { 395 | debug("Could not change folder: dir is no directory"); 396 | } else { 397 | final File[] contents = dir.listFiles(); 398 | if (contents != null) { 399 | int numDirectories = 0; 400 | for (final File f : contents) { 401 | if (f.isDirectory()) { 402 | numDirectories++; 403 | } 404 | } 405 | mFilesInDir = new File[numDirectories]; 406 | mFilenames.clear(); 407 | for (int i = 0, counter = 0; i < numDirectories; counter++) { 408 | if (contents[counter].isDirectory()) { 409 | mFilesInDir[i] = contents[counter]; 410 | mFilenames.add(contents[counter].getName()); 411 | i++; 412 | } 413 | } 414 | Arrays.sort(mFilesInDir); 415 | Collections.sort(mFilenames); 416 | mSelectedDir = dir; 417 | mTxtvSelectedFolder.setText(dir.getAbsolutePath()); 418 | mListDirectoriesAdapter.notifyDataSetChanged(); 419 | mFileObserver = createFileObserver(dir.getAbsolutePath()); 420 | mFileObserver.startWatching(); 421 | debug("Changed directory to %s", dir.getAbsolutePath()); 422 | } else { 423 | debug("Could not change folder: contents of dir were null"); 424 | } 425 | } 426 | refreshButtonState(); 427 | } 428 | 429 | /** 430 | * Changes the state of the buttons depending on the currently selected file 431 | * or folder. 432 | */ 433 | private void refreshButtonState() { 434 | final Activity activity = getActivity(); 435 | if (activity != null && mSelectedDir != null) { 436 | mBtnConfirm.setEnabled(isValidFile(mSelectedDir)); 437 | getActivity().invalidateOptionsMenu(); 438 | } 439 | } 440 | 441 | /** 442 | * Refresh the contents of the directory that is currently shown. 443 | */ 444 | private void refreshDirectory() { 445 | if (mSelectedDir != null) { 446 | changeDirectory(mSelectedDir); 447 | } 448 | } 449 | 450 | /** 451 | * Sets up a FileObserver to watch the current directory. 452 | */ 453 | private FileObserver createFileObserver(final String path) { 454 | return new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE 455 | | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) { 456 | 457 | @Override 458 | public void onEvent(final int event, final String path) { 459 | debug("FileObserver received event %d", event); 460 | final Activity activity = getActivity(); 461 | 462 | if (activity != null) { 463 | activity.runOnUiThread(new Runnable() { 464 | @Override 465 | public void run() { 466 | refreshDirectory(); 467 | } 468 | }); 469 | } 470 | } 471 | }; 472 | } 473 | 474 | /** 475 | * Returns the selected folder as a result to the activity the fragment's attached to. The 476 | * selected folder can also be null. 477 | */ 478 | private void returnSelectedFolder() { 479 | if (mSelectedDir != null) { 480 | debug("Returning %s as result", mSelectedDir.getAbsolutePath()); 481 | mListener.foreach(new UnitFunction() { 482 | @Override 483 | public void apply(final OnFragmentInteractionListener f) { 484 | f.onSelectDirectory(mSelectedDir.getAbsolutePath()); 485 | } 486 | }); 487 | } else { 488 | mListener.foreach(new UnitFunction() { 489 | @Override 490 | public void apply(final OnFragmentInteractionListener f) { 491 | f.onCancelChooser(); 492 | } 493 | }); 494 | } 495 | 496 | } 497 | 498 | /** 499 | * Creates a new folder in the current directory with the name 500 | * CREATE_DIRECTORY_NAME. 501 | */ 502 | private int createFolder() { 503 | if (mNewDirectoryName != null && mSelectedDir != null 504 | && mSelectedDir.canWrite()) { 505 | final File newDir = new File(mSelectedDir, mNewDirectoryName); 506 | if (newDir.exists()) { 507 | return R.string.create_folder_error_already_exists; 508 | } else { 509 | final boolean result = newDir.mkdir(); 510 | if (result) { 511 | return R.string.create_folder_success; 512 | } else { 513 | return R.string.create_folder_error; 514 | } 515 | } 516 | } else if (mSelectedDir != null && !mSelectedDir.canWrite()) { 517 | return R.string.create_folder_error_no_write_access; 518 | } else { 519 | return R.string.create_folder_error; 520 | } 521 | } 522 | 523 | /** 524 | * Returns true if the selected file or directory would be valid selection. 525 | */ 526 | private boolean isValidFile(final File file) { 527 | return (file != null && file.isDirectory() && file.canRead() && 528 | (mConfig.allowReadOnlyDirectory() || file.canWrite())); 529 | } 530 | 531 | @Nullable 532 | public OnFragmentInteractionListener getDirectoryChooserListener() { 533 | return mListener.get(); 534 | } 535 | 536 | public void setDirectoryChooserListener(@Nullable final OnFragmentInteractionListener listener) { 537 | mListener = Option.option(listener); 538 | } 539 | 540 | /** 541 | * This interface must be implemented by activities that contain this 542 | * fragment to allow an interaction in this fragment to be communicated 543 | * to the activity and potentially other fragments contained in that 544 | * activity. 545 | *

546 | * See the Android Training lesson Communicating with Other Fragments for more information. 549 | */ 550 | public interface OnFragmentInteractionListener { 551 | /** 552 | * Triggered when the user successfully selected their destination directory. 553 | */ 554 | void onSelectDirectory(@NonNull String path); 555 | 556 | /** 557 | * Advices the activity to remove the current fragment. 558 | */ 559 | void onCancelChooser(); 560 | } 561 | 562 | } 563 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/ic_action_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-hdpi/ic_action_create.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/ic_action_create_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-hdpi/ic_action_create_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/navigation_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-hdpi/navigation_up.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-hdpi/navigation_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-hdpi/navigation_up_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-ldpi/navigation_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-ldpi/navigation_up.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-ldpi/navigation_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-ldpi/navigation_up_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/ic_action_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-mdpi/ic_action_create.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/ic_action_create_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-mdpi/ic_action_create_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/navigation_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-mdpi/navigation_up.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-mdpi/navigation_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-mdpi/navigation_up_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/ic_action_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-xhdpi/ic_action_create.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/ic_action_create_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-xhdpi/ic_action_create_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/navigation_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-xhdpi/navigation_up.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/navigation_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-xhdpi/navigation_up_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxhdpi/ic_action_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-xxhdpi/ic_action_create.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xxhdpi/ic_action_create_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passy/Android-DirectoryChooser/0c4affef29609ec6ffa5ed1b829d229e28bab86d/library/src/main/res/drawable-xxhdpi/ic_action_create_light.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/borderless_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /library/src/main/res/layout-v11/directory_chooser.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 17 | 18 | 27 | 28 |