com.ultramegasoft.flavordex2.dialog.FileSelectorDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.ultramegasoft.flavordex2.dialog.FileSelectorDialog.java

Source

/*
 * The MIT License (MIT)
 * Copyright  2016 Steve Guidetti
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the Software?), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.ultramegasoft.flavordex2.dialog;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.ultramegasoft.flavordex2.R;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * Dialog for browsing and selecting files from the filesystem.
 *
 * @author Steve Guidetti
 */
@SuppressWarnings("SameParameterValue")
public class FileSelectorDialog extends DialogFragment {
    private static final String TAG = "FileSelectorDialog";

    /**
     * Arguments for the Fragment
     */
    private static final String ARG_PATH = "path";
    private static final String ARG_ROOT_PATH = "root_path";
    private static final String ARG_ALLOW_DIRECTORIES = "allow_directories";
    private static final String ARG_NAME_FILTER = "name_filter";

    /**
     * Keys for the result data Intent
     */
    private static final String EXTRA_PATH = "path";

    /**
     * Keys for the saved state
     */
    private static final String STATE_PATH = "path";

    /**
     * The current path
     */
    private String mPath;

    /**
     * The initial starting path
     */
    private String mRootPath;

    /**
     * Whether to allow directories to be selected
     */
    private boolean mAllowDirectories;

    /**
     * Filter out files that do not contain any of these strings
     */
    @Nullable
    private String[] mNameFilter;

    /**
     * The ListView from the layout
     */
    private ListView mListView;

    /**
     * The header view used to show the current directory and allow traversing up the tree
     */
    private TextView mHeader;

    /**
     * The view to show when the current directory is empty
     */
    private TextView mEmpty;

    /**
     * The Adapter backing the list
     */
    private FileListAdapter mAdapter;

    /**
     * Callback interface for Activities to receive results.
     */
    public interface OnFileSelectedCallbacks {
        /**
         * Called when a file is selected.
         *
         * @param filePath The path to the selected file
         */
        void onFileSelected(String filePath);
    }

    /**
     * @param fm               The FragmentManager to use
     * @param target           The Fragment to send results to
     * @param requestCode      The request code
     * @param rootPath         The initial starting path
     * @param allowDirectories Whether to allow directories to be selected
     * @param nameFilter       Filter out files that do not contain any of these string
     */
    public static void showDialog(@NonNull FragmentManager fm, @Nullable Fragment target, int requestCode,
            @Nullable String rootPath, boolean allowDirectories, @Nullable String[] nameFilter) {
        showDialog(fm, target, requestCode, rootPath, allowDirectories, nameFilter, rootPath);
    }

    /**
     * @param fm               The FragmentManager to use
     * @param target           The Fragment to send results to
     * @param requestCode      The request code
     * @param rootPath         The initial starting path
     * @param allowDirectories Whether to allow directories to be selected
     * @param nameFilter       Filter out files that do not contain any of these string
     * @param path             The current path
     */
    private static void showDialog(@NonNull FragmentManager fm, @Nullable Fragment target, int requestCode,
            @Nullable String rootPath, boolean allowDirectories, @Nullable String[] nameFilter,
            @Nullable String path) {
        final DialogFragment fragment = new FileSelectorDialog();
        fragment.setTargetFragment(target, requestCode);

        final Bundle args = new Bundle();
        args.putString(ARG_ROOT_PATH, rootPath);
        args.putString(ARG_PATH, path);
        args.putBoolean(ARG_ALLOW_DIRECTORIES, allowDirectories);
        args.putStringArray(ARG_NAME_FILTER, nameFilter);
        fragment.setArguments(args);

        fragment.show(fm, TAG);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Context context = getContext();
        if (context == null) {
            return;
        }

        final Bundle args = getArguments();
        if (args != null) {
            mPath = args.getString(ARG_PATH);
            mRootPath = args.getString(ARG_ROOT_PATH);
            mAllowDirectories = args.getBoolean(ARG_ALLOW_DIRECTORIES, false);
            mNameFilter = args.getStringArray(ARG_NAME_FILTER);

            if (mNameFilter != null) {
                for (int i = 0; i < mNameFilter.length; i++) {
                    mNameFilter[i] = mNameFilter[i].toLowerCase();
                }
            }
        }

        if (savedInstanceState != null) {
            mPath = savedInstanceState.getString(STATE_PATH, mPath);
        }

        if (mRootPath == null) {
            mRootPath = Environment.getExternalStorageDirectory().getPath();
        }

        if (mPath == null) {
            mPath = mRootPath;
        }

        if (!new File(mPath).canRead()) {
            Toast.makeText(context, R.string.error_read_dir, Toast.LENGTH_LONG).show();
            dismiss();
            return;
        }

        mAdapter = new FileListAdapter(context);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setupAdapter();
    }

    @NonNull
    @Override
    @SuppressLint("InflateParams")
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final Context context = getContext();
        if (context == null) {
            return super.onCreateDialog(savedInstanceState);
        }

        final View root = LayoutInflater.from(context).inflate(R.layout.list_dialog, null);
        mListView = root.findViewById(R.id.list);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                selectItem(position);
            }
        });

        mEmpty = root.findViewById(R.id.empty);
        if (mNameFilter == null) {
            mEmpty.setText(R.string.message_empty_dir);
        } else {
            mEmpty.setText(R.string.message_empty_dir_filtered);
        }
        mEmpty.setVisibility(View.VISIBLE);
        ((ViewGroup) root.findViewById(R.id.list_container)).removeView(mEmpty);
        mEmpty.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));

        final AlertDialog.Builder builder = new AlertDialog.Builder(context).setTitle(R.string.title_select_file)
                .setView(root).setNegativeButton(R.string.button_cancel, null);

        if (mAllowDirectories) {
            builder.setPositiveButton(R.string.button_select_dir, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    selectCurrentFile();
                }
            });
        }

        return builder.create();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(STATE_PATH, mPath);
    }

    /**
     * Called when an item is selected.
     *
     * @param index The item index
     */
    private void selectItem(int index) {
        final ListAdapter adapter = mListView.getAdapter();
        final int type = adapter.getItemViewType(index);
        final String item = (String) adapter.getItem(index);

        final File file = new File(mPath);
        if (type == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            mPath = file.getParentFile().getPath();
        } else {
            mPath = new File(file, item).getPath();
        }

        if (type == FileListAdapter.FILE_TYPE) {
            selectCurrentFile();
        } else {
            setupAdapter();
        }
    }

    /**
     * Select a file and send the result back to the target Fragment.
     */
    private void selectCurrentFile() {
        final Fragment target = getTargetFragment();
        if (target != null) {
            final Intent data = new Intent();
            data.putExtra(EXTRA_PATH, mPath);
            target.onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data);
        } else if (getActivity() instanceof OnFileSelectedCallbacks) {
            ((OnFileSelectedCallbacks) getActivity()).onFileSelected(mPath);
        }
        dismiss();
    }

    /**
     * Set up the Adapter with the current list of files.
     */
    private void setupAdapter() {
        final File file = new File(mPath);
        if (!file.canRead() || !file.isDirectory()) {
            mAdapter.reset();
            return;
        }

        mListView.setAdapter(null);

        mAdapter.setData(getFileList(file), getDirList(file));
        setHeader(mPath.equals(mRootPath) ? null : file.getName());
        showEmpty(mAdapter.isEmpty());

        mListView.setAdapter(mAdapter);
    }

    /**
     * Set the header text.
     *
     * @param header The header text or null to remove the header
     */
    private void setHeader(@Nullable String header) {
        if (mHeader == null) {
            mHeader = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.file_list_item_dir_open,
                    mListView, false);
        }
        mListView.removeHeaderView(mHeader);
        if (header != null) {
            mHeader.setText(header);
            mListView.addHeaderView(mHeader);
        }
    }

    /**
     * Show or hide the empty directory text.
     *
     * @param show Whether to show the empty directory text
     */
    private void showEmpty(boolean show) {
        mListView.removeFooterView(mEmpty);
        if (show) {
            mListView.addFooterView(mEmpty, null, false);
        }
    }

    /**
     * Get the list of files in the given path that match the current name filter.
     *
     * @param path The path to a directory to list files from
     * @return An array of file names
     */
    @NonNull
    private String[] getFileList(@NonNull File path) {
        final FilenameFilter filenameFilter = new FilenameFilter() {
            public boolean accept(File dir, String filename) {
                final File file = new File(dir, filename);
                return !(!file.canRead() || file.isDirectory() || filename.startsWith("."))
                        && matchesFilter(filename);
            }
        };

        final String[] fileList = path.list(filenameFilter);
        if (fileList == null) {
            return new String[0];
        }
        Arrays.sort(fileList, String.CASE_INSENSITIVE_ORDER);

        return fileList;
    }

    /**
     * Check if a string matches the file name filter.
     *
     * @param string The string to check
     * @return Whether the string matches the filter
     */
    private boolean matchesFilter(@NonNull String string) {
        if (mNameFilter == null || mNameFilter.length < 1) {
            return true;
        }

        string = string.toLowerCase();
        for (String s : mNameFilter) {
            if (string.contains(s)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get the list of directories in the given path.
     *
     * @param path The path to a directory to list files from
     * @return An array of directory names
     */
    @NonNull
    private String[] getDirList(@NonNull File path) {
        final FilenameFilter dirFilter = new FilenameFilter() {
            public boolean accept(File dir, String filename) {
                final File file = new File(dir, filename);
                return file.canRead() && file.isDirectory() && !filename.startsWith(".");
            }
        };

        final String[] dirList = path.list(dirFilter);
        if (dirList == null) {
            return new String[0];
        }
        Arrays.sort(dirList, String.CASE_INSENSITIVE_ORDER);

        return dirList;
    }

    /**
     * Custom Adapter for listing files.
     */
    private static class FileListAdapter extends BaseAdapter {
        /**
         * Item type IDs
         */
        static final int FILE_TYPE = 0;
        static final int DIR_TYPE = 1;

        /**
         * The Context
         */
        @NonNull
        private final Context mContext;

        /**
         * The list of file names
         */
        private final ArrayList<String> mData = new ArrayList<>();

        /**
         * The list of types corresponding to each file
         */
        private final ArrayList<Integer> mTypes = new ArrayList<>();

        /**
         * @param context The Context
         */
        FileListAdapter(@NonNull Context context) {
            mContext = context;
        }

        /**
         * Reset the data backing the Adapter.
         */
        void reset() {
            mData.clear();
            mTypes.clear();
            notifyDataSetChanged();
        }

        /**
         * Set the data backing the Adapter.
         *
         * @param files The list of file names
         * @param dirs  The list of directory names
         */
        void setData(@NonNull String[] files, @NonNull String[] dirs) {
            reset();
            addItems(dirs, DIR_TYPE);
            addItems(files, FILE_TYPE);
        }

        /**
         * Add items to the list.
         *
         * @param items The item names
         * @param type  The item type
         */
        void addItems(@NonNull String[] items, int type) {
            for (String item : items) {
                mData.add(item);
                mTypes.add(type);
            }
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public String getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        @SuppressWarnings("MethodDoesntCallSuperMethod")
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        @SuppressWarnings("MethodDoesntCallSuperMethod")
        public int getItemViewType(int position) {
            return mTypes.get(position);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                final int id = getItemViewType(position) == DIR_TYPE ? R.layout.file_list_item_dir
                        : R.layout.file_list_item;
                convertView = LayoutInflater.from(mContext).inflate(id, parent, false);
            }

            ((TextView) convertView).setText(mData.get(position));

            return convertView;
        }
    }
}