paulscode.android.mupen64plusae.preference.PathPreference.java Source code

Java tutorial

Introduction

Here is the source code for paulscode.android.mupen64plusae.preference.PathPreference.java

Source

/**
 * Mupen64PlusAE, an N64 emulator for the Android platform
 * 
 * Copyright (C) 2013 Paul Lamb
 * 
 * This file is part of Mupen64PlusAE.
 * 
 * Mupen64PlusAE is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * Mupen64PlusAE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with Mupen64PlusAE. If
 * not, see <http://www.gnu.org/licenses/>.
 * 
 * Authors: littleguy77
 */
package paulscode.android.mupen64plusae.preference;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.mupen64plusae.v3.alpha.R;

import paulscode.android.mupen64plusae.compat.AppCompatPreferenceActivity.OnPreferenceDialogListener;
import paulscode.android.mupen64plusae.dialog.Prompt;
import paulscode.android.mupen64plusae.util.FileUtil;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.os.Environment;
import android.os.Parcelable;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.preference.DialogPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ArrayAdapter;

/**
 * A {@link DialogPreference} that is specifically for choosing a directory path or file on a device.
 */
public class PathPreference extends DialogPreference
        implements OnPreferenceDialogListener, DialogInterface.OnClickListener {
    /** The user must select a directory. No files will be shown in the list. */
    public static final int SELECTION_MODE_DIRECTORY = 0;

    /** The user must select a file. The dialog will only close when a file is selected. */
    public static final int SELECTION_MODE_FILE = 1;

    /** The user may select a file or a directory. The Ok button must be used. */
    public static final int SELECTION_MODE_ANY = 2;

    private static final String STORAGE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath();

    private final boolean mUseDefaultSummary;
    private int mSelectionMode = SELECTION_MODE_ANY;
    private boolean mDoReclick = false;
    private final List<CharSequence> mNames = new ArrayList<CharSequence>();
    private final List<String> mPaths = new ArrayList<String>();
    private String mNewValue;
    private String mValue;

    /**
     * Constructor
     *
     * @param context The {@link Context} that this PathPreference is being used in.
     * @param attrs   A collection of attributes, as found associated with a tag in an XML document.
     */
    public PathPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        mUseDefaultSummary = TextUtils.isEmpty(getSummary());

        // Get the selection mode from the XML file, if provided
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PathPreference);
        mSelectionMode = a.getInteger(R.styleable.PathPreference_selectionMode, SELECTION_MODE_ANY);
        a.recycle();

        setOnPreferenceChangeListener(null);
    }

    /**
     * Sets the path that PathPrefence will use.
     * 
     * @param value The path that this PathPreference instance will use.
     */
    public void setValue(String value) {
        mValue = validate(value);
        if (shouldPersist())
            persistString(mValue);

        // Summary always reflects the true/persisted value, does not track the temporary/new value
        if (mUseDefaultSummary)
            setSummary(mSelectionMode == SELECTION_MODE_FILE ? new File(mValue).getName() : mValue);

        // Reset the dialog info
        populate(mValue);
    }

    /**
     * Sets the specific selection mode to use.
     * 
     * @param value The selection mode to use.</p>
     *              <li>0 = Directories can only be used as a choice.
     *              <li>1 = Files can only be used as a choice.
     *              <li>2 = Directories and files can be used as a choice.</li>
     */
    public void setSelectionMode(int value) {
        mSelectionMode = value;
    }

    /**
     * Gets the path value being used.
     * 
     * @return The path value being used by this PathPreference.
     */
    public String getValue() {
        return mValue;
    }

    /**
     * Gets the current selection mode being used.
     * 
     * @return The current selection mode being used by this PathPreference.
     */
    public int getSelectionMode() {
        return mSelectionMode;
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getString(index);
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        setValue(restorePersistedValue ? getPersistedString(mValue) : (String) defaultValue);
    }

    @Override
    public void onPrepareDialogBuilder(Context context, Builder builder) {
        // Add the list entries

        // Holo theme has folder icons and "Parent folder" text
        ArrayAdapter<String> adapter = Prompt.createFilenameAdapter(getContext(), mPaths, mNames);
        builder.setAdapter(adapter, this);

        // Remove the Ok button when user must choose a file
        if (mSelectionMode == SELECTION_MODE_FILE)
            builder.setPositiveButton(null, null);
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        // If the user clicked a list item...
        if (which >= 0 && which < mPaths.size()) {
            mNewValue = mPaths.get(which);
            File path = new File(mNewValue);
            if (path.isDirectory()) {
                // ...navigate into...
                populate(mNewValue);
                mDoReclick = true;
            } else {
                // ...or close dialog positively
                which = DialogInterface.BUTTON_POSITIVE;
            }
        }
    }

    @Override
    public void onDialogClosed(boolean positiveResult) {
        if (positiveResult && callChangeListener(mNewValue)) {
            // User clicked Ok: clean the state by persisting value
            setValue(mNewValue);
        } else if (mDoReclick) {
            // User clicked a list item: maintain dirty value and re-open
            mDoReclick = false;
            onClick();
        } else {
            // User clicked Cancel/Back: clean state by restoring persisted value
            populate(mValue);
        }
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        final SavedStringState myState = new SavedStringState(super.onSaveInstanceState());
        myState.mValue = mNewValue;
        return myState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state == null || !state.getClass().equals(SavedStringState.class)) {
            // Didn't save state for us in onSaveInstanceState
            super.onRestoreInstanceState(state);
            return;
        }

        final SavedStringState myState = (SavedStringState) state;
        super.onRestoreInstanceState(myState.getSuperState());
        populate(myState.mValue);
    }

    // Populates the dialog view with files and folders on the device.
    private void populate(String path) {
        // Cache the path to persist on Ok
        mNewValue = path;

        // Quick exit if null
        if (path == null)
            return;

        // If start path is a file, list it and its siblings in the parent directory
        File startPath = new File(path);
        if (startPath.isFile())
            startPath = startPath.getParentFile();

        // Set the dialog title based on the selection mode
        switch (mSelectionMode) {
        case SELECTION_MODE_FILE:
            // If selecting only files, set title to parent directory name
            setDialogTitle(startPath.getPath());
            break;
        case SELECTION_MODE_DIRECTORY:
        case SELECTION_MODE_ANY:
            // Otherwise clarify the directory that will be selected if user clicks Ok
            setDialogTitle(getContext().getString(R.string.pathPreference_dialogTitle, startPath.getPath()));
            break;
        }

        // Populate the key-value pairs for the list entries
        boolean isFilesIncluded = mSelectionMode != SELECTION_MODE_DIRECTORY;
        FileUtil.populate(startPath, true, true, isFilesIncluded, mNames, mPaths);
    }

    public static String validate(String value) {
        if (TextUtils.isEmpty(value)) {
            // Use storage directory if value is empty
            value = STORAGE_DIR;
        } else {
            // Non-empty string provided
            // Prefixes encode additional information:
            // ! and ~ mean path is relative to storage dir
            // ! means parent dirs should be created if path does not exist
            // ~ means storage dir should be used if path does not exist
            boolean isRelativePath = value.startsWith("!") || value.startsWith("~");
            boolean forceParentDirs = value.startsWith("!");

            // Build the absolute path if necessary
            if (isRelativePath)
                value = STORAGE_DIR + "/" + value.substring(1);

            // Ensure the parent directories exist if requested
            File file = new File(value);
            if (forceParentDirs)
                file.mkdirs();
            else if (!file.exists())
                value = STORAGE_DIR;
        }
        return value;
    }

    @Override
    public void onBindDialogView(View view, FragmentActivity associatedActivity) {
        //Nothing to do here
    }
}