FileChooserDialog.java 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package com.afollestad.materialdialogs.folderselector;
  2. import android.Manifest;
  3. import android.app.Activity;
  4. import android.app.Dialog;
  5. import android.content.pm.PackageManager;
  6. import android.os.Build;
  7. import android.os.Bundle;
  8. import android.os.Environment;
  9. import android.support.annotation.NonNull;
  10. import android.support.annotation.Nullable;
  11. import android.support.annotation.StringRes;
  12. import android.support.v4.app.ActivityCompat;
  13. import android.support.v4.app.DialogFragment;
  14. import android.support.v4.app.Fragment;
  15. import android.support.v7.app.AppCompatActivity;
  16. import android.view.View;
  17. import android.webkit.MimeTypeMap;
  18. import com.afollestad.materialdialogs.DialogAction;
  19. import com.afollestad.materialdialogs.MaterialDialog;
  20. import com.afollestad.materialdialogs.commons.R;
  21. import java.io.File;
  22. import java.io.Serializable;
  23. import java.util.ArrayList;
  24. import java.util.Collections;
  25. import java.util.Comparator;
  26. import java.util.List;
  27. public class FileChooserDialog extends DialogFragment implements MaterialDialog.ListCallback {
  28. private final static String TAG = "[MD_FILE_SELECTOR]";
  29. private File parentFolder;
  30. private File[] parentContents;
  31. private boolean canGoUp = true;
  32. private FileCallback mCallback;
  33. public interface FileCallback {
  34. void onFileSelection(@NonNull File file);
  35. }
  36. public FileChooserDialog() {
  37. }
  38. String[] getContentsArray() {
  39. if (parentContents == null) return new String[]{};
  40. String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
  41. if (canGoUp) results[0] = "...";
  42. for (int i = 0; i < parentContents.length; i++)
  43. results[canGoUp ? i + 1 : i] = parentContents[i].getName();
  44. return results;
  45. }
  46. File[] listFiles(String mimeType) {
  47. File[] contents = parentFolder.listFiles();
  48. List<File> results = new ArrayList<>();
  49. if (contents != null) {
  50. MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
  51. for (File fi : contents) {
  52. if (fi.isDirectory()) {
  53. results.add(fi);
  54. } else {
  55. if (fileIsMimeType(fi, mimeType, mimeTypeMap)) {
  56. results.add(fi);
  57. }
  58. }
  59. }
  60. Collections.sort(results, new FileSorter());
  61. return results.toArray(new File[results.size()]);
  62. }
  63. return null;
  64. }
  65. boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) {
  66. if (mimeType == null || mimeType.equals("*/*")) {
  67. return true;
  68. } else {
  69. // get the file mime type
  70. String filename = file.toURI().toString();
  71. int dotPos = filename.lastIndexOf('.');
  72. if (dotPos == -1) {
  73. return false;
  74. }
  75. String fileExtension = filename.substring(dotPos + 1);
  76. String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension);
  77. if (fileType == null) {
  78. return false;
  79. }
  80. // check the 'type/subtype' pattern
  81. if (fileType.equals(mimeType)) {
  82. return true;
  83. }
  84. // check the 'type/*' pattern
  85. int mimeTypeDelimiter = mimeType.lastIndexOf('/');
  86. if (mimeTypeDelimiter == -1) {
  87. return false;
  88. }
  89. String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter);
  90. String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1);
  91. if (!mimeTypeSubtype.equals("*")) {
  92. return false;
  93. }
  94. int fileTypeDelimiter = fileType.lastIndexOf('/');
  95. if (fileTypeDelimiter == -1) {
  96. return false;
  97. }
  98. String fileTypeMainType = fileType.substring(0, fileTypeDelimiter);
  99. if (fileTypeMainType.equals(mimeTypeMainType)) {
  100. return true;
  101. }
  102. }
  103. return false;
  104. }
  105. @SuppressWarnings("ConstantConditions")
  106. @NonNull
  107. @Override
  108. public Dialog onCreateDialog(Bundle savedInstanceState) {
  109. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
  110. ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) !=
  111. PackageManager.PERMISSION_GRANTED) {
  112. return new MaterialDialog.Builder(getActivity())
  113. .title(R.string.md_error_label)
  114. .content(R.string.md_storage_perm_error)
  115. .positiveText(android.R.string.ok)
  116. .build();
  117. }
  118. if (getArguments() == null || !getArguments().containsKey("builder"))
  119. throw new IllegalStateException("You must create a FileChooserDialog using the Builder.");
  120. if (!getArguments().containsKey("current_path"))
  121. getArguments().putString("current_path", getBuilder().mInitialPath);
  122. parentFolder = new File(getArguments().getString("current_path"));
  123. parentContents = listFiles(getBuilder().mMimeType);
  124. return new MaterialDialog.Builder(getActivity())
  125. .title(parentFolder.getAbsolutePath())
  126. .items(getContentsArray())
  127. .itemsCallback(this)
  128. .onNegative(new MaterialDialog.SingleButtonCallback() {
  129. @Override
  130. public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
  131. dialog.dismiss();
  132. }
  133. })
  134. .autoDismiss(false)
  135. .negativeText(getBuilder().mCancelButton)
  136. .positiveText(getBuilder().mChooseButton)
  137. .build();
  138. }
  139. @Override
  140. public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
  141. if (canGoUp && i == 0) {
  142. parentFolder = parentFolder.getParentFile();
  143. if (parentFolder.getAbsolutePath().equals("/storage/emulated"))
  144. parentFolder = parentFolder.getParentFile();
  145. canGoUp = parentFolder.getParent() != null;
  146. } else {
  147. parentFolder = parentContents[canGoUp ? i - 1 : i];
  148. canGoUp = true;
  149. if (parentFolder.getAbsolutePath().equals("/storage/emulated"))
  150. parentFolder = Environment.getExternalStorageDirectory();
  151. }
  152. if (parentFolder.isFile()) {
  153. mCallback.onFileSelection(parentFolder);
  154. dismiss();
  155. } else {
  156. parentContents = listFiles(getBuilder().mMimeType);
  157. MaterialDialog dialog = (MaterialDialog) getDialog();
  158. dialog.setTitle(parentFolder.getAbsolutePath());
  159. getArguments().putString("current_path", parentFolder.getAbsolutePath());
  160. dialog.setItems(getContentsArray());
  161. }
  162. }
  163. @Override
  164. public void onAttach(Activity activity) {
  165. super.onAttach(activity);
  166. mCallback = (FileCallback) activity;
  167. }
  168. public void show(AppCompatActivity context) {
  169. Fragment frag = context.getSupportFragmentManager().findFragmentByTag(TAG);
  170. if (frag != null) {
  171. ((DialogFragment) frag).dismiss();
  172. context.getSupportFragmentManager().beginTransaction()
  173. .remove(frag).commit();
  174. }
  175. show(context.getSupportFragmentManager(), TAG);
  176. }
  177. public static class Builder implements Serializable {
  178. @NonNull
  179. protected final transient AppCompatActivity mContext;
  180. @StringRes
  181. protected int mChooseButton;
  182. @StringRes
  183. protected int mCancelButton;
  184. protected String mInitialPath;
  185. protected String mMimeType;
  186. public <ActivityType extends AppCompatActivity & FileCallback> Builder(@NonNull ActivityType context) {
  187. mContext = context;
  188. mCancelButton = android.R.string.cancel;
  189. mChooseButton = R.string.md_choose_label;
  190. mInitialPath = Environment.getExternalStorageDirectory().getAbsolutePath();
  191. mMimeType = null;
  192. }
  193. @NonNull
  194. public Builder cancelButton(@StringRes int text) {
  195. mCancelButton = text;
  196. return this;
  197. }
  198. @NonNull
  199. public Builder chooseButton(@StringRes int text) {
  200. mChooseButton = text;
  201. return this;
  202. }
  203. @NonNull
  204. public Builder initialPath(@Nullable String initialPath) {
  205. if (initialPath == null)
  206. initialPath = File.separator;
  207. mInitialPath = initialPath;
  208. return this;
  209. }
  210. @NonNull
  211. public Builder mimeType(@Nullable String type) {
  212. mMimeType = type;
  213. return this;
  214. }
  215. @NonNull
  216. public FileChooserDialog build() {
  217. FileChooserDialog dialog = new FileChooserDialog();
  218. Bundle args = new Bundle();
  219. args.putSerializable("builder", this);
  220. dialog.setArguments(args);
  221. return dialog;
  222. }
  223. @NonNull
  224. public FileChooserDialog show() {
  225. FileChooserDialog dialog = build();
  226. dialog.show(mContext);
  227. return dialog;
  228. }
  229. }
  230. @SuppressWarnings("ConstantConditions")
  231. @NonNull
  232. private Builder getBuilder() {
  233. return (Builder) getArguments().getSerializable("builder");
  234. }
  235. private static class FileSorter implements Comparator<File> {
  236. @Override
  237. public int compare(File lhs, File rhs) {
  238. return lhs.getName().compareTo(rhs.getName());
  239. }
  240. }
  241. }