package com.afollestad.materialdialogs.folderselector; import android.Manifest; import android.app.Activity; import android.app.Dialog; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.v13.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.webkit.MimeTypeMap; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.commons.R; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class FileChooserDialog extends DialogFragment implements MaterialDialog.ListCallback { private static final String DEFAULT_TAG = "[MD_FILE_SELECTOR]"; private File parentFolder; private File[] parentContents; private boolean canGoUp = true; private FileCallback callback; public FileChooserDialog() {} CharSequence[] getContentsArray() { if (parentContents == null) { if (canGoUp) { return new String[] {getBuilder().goUpLabel}; } return new String[] {}; } String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; if (canGoUp) { results[0] = getBuilder().goUpLabel; } for (int i = 0; i < parentContents.length; i++) { results[canGoUp ? i + 1 : i] = parentContents[i].getName(); } return results; } File[] listFiles(@Nullable String mimeType, @Nullable String[] extensions) { File[] contents = parentFolder.listFiles(); List<File> results = new ArrayList<>(); if (contents != null) { MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); for (File fi : contents) { if (fi.isDirectory()) { results.add(fi); } else { if (extensions != null) { boolean found = false; for (String ext : extensions) { if (fi.getName().toLowerCase().endsWith(ext.toLowerCase())) { found = true; break; } } if (found) { results.add(fi); } } else if (mimeType != null) { if (fileIsMimeType(fi, mimeType, mimeTypeMap)) { results.add(fi); } } } } Collections.sort(results, new FileSorter()); return results.toArray(new File[results.size()]); } return null; } boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) { if (mimeType == null || mimeType.equals("*/*")) { return true; } else { // get the file mime type String filename = file.toURI().toString(); int dotPos = filename.lastIndexOf('.'); if (dotPos == -1) { return false; } String fileExtension = filename.substring(dotPos + 1); if (fileExtension.endsWith("json")) { return mimeType.startsWith("application/json"); } String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension); if (fileType == null) { return false; } // check the 'type/subtype' pattern if (fileType.equals(mimeType)) { return true; } // check the 'type/*' pattern int mimeTypeDelimiter = mimeType.lastIndexOf('/'); if (mimeTypeDelimiter == -1) { return false; } String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter); String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1); if (!mimeTypeSubtype.equals("*")) { return false; } int fileTypeDelimiter = fileType.lastIndexOf('/'); if (fileTypeDelimiter == -1) { return false; } String fileTypeMainType = fileType.substring(0, fileTypeDelimiter); if (fileTypeMainType.equals(mimeTypeMainType)) { return true; } } return false; } @SuppressWarnings("ConstantConditions") @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission( getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { return new MaterialDialog.Builder(getActivity()) .title(R.string.md_error_label) .content(R.string.md_storage_perm_error) .positiveText(android.R.string.ok) .build(); } if (getArguments() == null || !getArguments().containsKey("builder")) { throw new IllegalStateException("You must create a FileChooserDialog using the Builder."); } if (!getArguments().containsKey("current_path")) { getArguments().putString("current_path", getBuilder().initialPath); } parentFolder = new File(getArguments().getString("current_path")); checkIfCanGoUp(); parentContents = listFiles(getBuilder().mimeType, getBuilder().extensions); return new MaterialDialog.Builder(getActivity()) .title(parentFolder.getAbsolutePath()) .typeface(getBuilder().mediumFont, getBuilder().regularFont) .items(getContentsArray()) .itemsCallback(this) .onNegative( new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { dialog.dismiss(); } }) .autoDismiss(false) .negativeText(getBuilder().cancelButton) .build(); } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); if (callback != null) { callback.onFileChooserDismissed(this); } } @Override public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { if (canGoUp && i == 0) { parentFolder = parentFolder.getParentFile(); if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { parentFolder = parentFolder.getParentFile(); } canGoUp = parentFolder.getParent() != null; } else { parentFolder = parentContents[canGoUp ? i - 1 : i]; canGoUp = true; if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { parentFolder = Environment.getExternalStorageDirectory(); } } if (parentFolder.isFile()) { callback.onFileSelection(this, parentFolder); dismiss(); } else { parentContents = listFiles(getBuilder().mimeType, getBuilder().extensions); MaterialDialog dialog = (MaterialDialog) getDialog(); dialog.setTitle(parentFolder.getAbsolutePath()); getArguments().putString("current_path", parentFolder.getAbsolutePath()); dialog.setItems(getContentsArray()); } } private void checkIfCanGoUp() { try { canGoUp = parentFolder.getPath().split("/").length > 1; } catch (IndexOutOfBoundsException e) { canGoUp = false; } } @Override public void onAttach(Activity activity) { super.onAttach(activity); callback = (FileCallback) activity; } public void show(FragmentActivity context) { final String tag = getBuilder().tag; Fragment frag = context.getSupportFragmentManager().findFragmentByTag(tag); if (frag != null) { ((DialogFragment) frag).dismiss(); context.getSupportFragmentManager().beginTransaction().remove(frag).commit(); } show(context.getSupportFragmentManager(), tag); } @NonNull public String getInitialPath() { return getBuilder().initialPath; } @SuppressWarnings("ConstantConditions") @NonNull private Builder getBuilder() { return (Builder) getArguments().getSerializable("builder"); } public interface FileCallback { void onFileSelection(@NonNull FileChooserDialog dialog, @NonNull File file); void onFileChooserDismissed(@NonNull FileChooserDialog dialog); } public static class Builder implements Serializable { @NonNull final transient AppCompatActivity context; @StringRes int cancelButton; String initialPath; String mimeType; String[] extensions; String tag; String goUpLabel; @Nullable String mediumFont; @Nullable String regularFont; public <ActivityType extends AppCompatActivity & FileCallback> Builder( @NonNull ActivityType context) { this.context = context; cancelButton = android.R.string.cancel; initialPath = Environment.getExternalStorageDirectory().getAbsolutePath(); mimeType = null; goUpLabel = "..."; } @NonNull public Builder typeface(@Nullable String medium, @Nullable String regular) { this.mediumFont = medium; this.regularFont = regular; return this; } @NonNull public Builder cancelButton(@StringRes int text) { cancelButton = text; return this; } @NonNull public Builder initialPath(@Nullable String initialPath) { if (initialPath == null) { initialPath = File.separator; } this.initialPath = initialPath; return this; } @NonNull public Builder mimeType(@Nullable String type) { mimeType = type; return this; } @NonNull public Builder extensionsFilter(@Nullable String... extensions) { this.extensions = extensions; return this; } @NonNull public Builder tag(@Nullable String tag) { if (tag == null) { tag = DEFAULT_TAG; } this.tag = tag; return this; } @NonNull public Builder goUpLabel(String text) { goUpLabel = text; return this; } @NonNull public FileChooserDialog build() { FileChooserDialog dialog = new FileChooserDialog(); Bundle args = new Bundle(); args.putSerializable("builder", this); dialog.setArguments(args); return dialog; } @NonNull public FileChooserDialog show() { FileChooserDialog dialog = build(); dialog.show(context); return dialog; } } private static class FileSorter implements Comparator<File> { @Override public int compare(File lhs, File rhs) { if (lhs.isDirectory() && !rhs.isDirectory()) { return -1; } else if (!lhs.isDirectory() && rhs.isDirectory()) { return 1; } else { return lhs.getName().compareTo(rhs.getName()); } } } }