package de.jeisfeld.augendiagnoselib.fragments; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Stack; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnKeyListener; import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import de.jeisfeld.augendiagnoselib.Application; import de.jeisfeld.augendiagnoselib.R; /** * Class to present a dialog for selection of a directory. * * <p>Inspired by http://www.codeproject.com/Articles/547636/Android-Ready-to-use-simple-directory-chooser-dial */ public class DirectoryChooserDialogFragment extends DialogFragment { /** * The text view showing the current folder. */ private TextView mCurrentFolderView; /** * The current folder. */ @Nullable private String mDir = ""; /** * The sub-elements of the current folder. */ @Nullable private List<String> mSubdirs = null; /** * The list adapter handling the sub-elements of the current folder. */ @Nullable private ArrayAdapter<String> mListAdapter = null; /** * The backstack of last browsed folders. */ private final Stack<String> mBackStack = new Stack<>(); /** * Create a DirectoryChooserDialogFragment. * * @param activity The activity calling the dialog. * @param listener The callback listener reacting on the dialog response. * @param dir The start folder. */ public static void displayDirectoryChooserDialog(@NonNull final Activity activity, final ChosenDirectoryListener listener, final String dir) { DirectoryChooserDialogFragment fragment = new DirectoryChooserDialogFragment(); Bundle bundle = new Bundle(); bundle.putString("dir", dir); bundle.putSerializable("listener", listener); fragment.setArguments(bundle); fragment.show(activity.getFragmentManager(), DirectoryChooserDialogFragment.class.toString()); } /* * Instantiate the view of the dialog. */ @Override public final Dialog onCreateDialog(final Bundle savedInstanceState) { // retrieve arguments String dir = getArguments().getString("dir"); final ChosenDirectoryListener listener = (ChosenDirectoryListener) getArguments().getSerializable("listener"); if (dir == null) { dir = getSdCardDirectory(); } File dirFile = new File(dir); if (!dirFile.exists() || !dirFile.isDirectory()) { dir = getSdCardDirectory(); } try { dir = new File(dir).getCanonicalPath(); } catch (IOException ioe) { return null; } mDir = dir; mSubdirs = getDirectories(dir); Context context = getActivity(); // Create dialog AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); dialogBuilder.setTitle(R.string.title_dialog_select_folder); View layout = LayoutInflater.from(context).inflate(R.layout.dialog_directory_chooser, new LinearLayout(context)); dialogBuilder.setView(layout); mCurrentFolderView = (TextView) layout.findViewById(R.id.textCurrentFolder); mCurrentFolderView.setText(dir); ListView listView = (ListView) layout.findViewById(R.id.listViewSubfolders); mListAdapter = createListAdapter(mSubdirs); listView.setAdapter(mListAdapter); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { mBackStack.push(mDir); mDir += File.separator + mListAdapter.getItem(position); updateDirectory(); } }); dialogBuilder.setCancelable(false); dialogBuilder.setPositiveButton(R.string.button_select, new OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { // Current directory chosen if (listener != null) { // Call registered listener supplied with the chosen directory listener.onChosenDir(mDir); } } }).setNegativeButton(R.string.button_cancel, new OnClickListener() { @Override public void onClick(final DialogInterface dialog, final int which) { if (listener != null) { listener.onCancelled(); } } }); final AlertDialog dirsDialog = dialogBuilder.create(); dirsDialog.setOnKeyListener(new OnKeyListener() { @Override public boolean onKey(final DialogInterface dialog, final int keyCode, @NonNull final KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { // Back button pressed - go to the last directory if existing - otherwise cancel the dialog. if (mBackStack.size() == 0) { dirsDialog.dismiss(); if (listener != null) { listener.onCancelled(); } } else { mDir = mBackStack.pop(); updateDirectory(); } return true; } else { return false; } } }); return dirsDialog; } /** * Get the list of subdirectories of the current directory. Returns ".." as first value if appropriate. * * @param dir The current directory. * @return The list of subdirectories. */ @NonNull private List<String> getDirectories(@Nullable final String dir) { List<String> dirs = new ArrayList<>(); if (dir != null && dir.startsWith(File.separator) && !dir.equals(File.separator)) { dirs.add(".."); } try { File dirFile = new File(dir); if (!dirFile.exists() || !dirFile.isDirectory()) { return dirs; } File[] files = dirFile.listFiles(); if (files == null) { return dirs; } for (File file : files) { if (file.isDirectory()) { dirs.add(file.getName()); } } } catch (Exception e) { Log.e(Application.TAG, "Could not get directories", e); } Collections.sort(dirs, new Comparator<String>() { @Override public int compare(@NonNull final String o1, @NonNull final String o2) { return o1.compareTo(o2); } }); return dirs; } /** * Update the current directory. */ private void updateDirectory() { try { mDir = new File(mDir).getCanonicalPath(); } catch (IOException e) { // i } if (mDir == null || "".equals(mDir)) { mDir = File.separator; } mSubdirs.clear(); mSubdirs.addAll(getDirectories(mDir)); mCurrentFolderView.setText(mDir); mListAdapter.notifyDataSetChanged(); } /** * Create the list adapter for the list of folders. * * @param items The list of folders. * @return The list adapter. */ @NonNull private ArrayAdapter<String> createListAdapter(@NonNull final List<String> items) { return new ArrayAdapter<String>(getActivity(), R.layout.adapter_list_names, android.R.id.text1, items) { @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View v = super.getView(position, convertView, parent); if (v instanceof TextView) { // Enable list item (directory) text wrapping TextView tv = (TextView) v; tv.getLayoutParams().height = LayoutParams.WRAP_CONTENT; tv.setEllipsize(null); } return v; } }; } /** * Get the SD card directory. * * @return The SD card directory. */ @NonNull private String getSdCardDirectory() { String sdCardDirectory = Environment.getExternalStorageDirectory().getAbsolutePath(); try { sdCardDirectory = new File(sdCardDirectory).getCanonicalPath(); } catch (IOException ioe) { Log.e(Application.TAG, "Could not get SD directory", ioe); } return sdCardDirectory; } /** * Callback interface for selected directory. */ public interface ChosenDirectoryListener extends Serializable { /** * Called when a folder is selected. * * @param chosenDir The selected folder. */ void onChosenDir(final String chosenDir); /** * Called when the dialog is cancelled. */ void onCancelled(); } }