0

I have a java class called SongsManager that is supposed to locate all the audio files on an SD Card. Unfortunately I am not able to locate any audio files with this code.

On my phone I have multiple .mp3 and .wma files in the following locations for testing:

  • Card
  • Card\music
  • Card\Android\data\com.samsung.music

Currently I am using the following for the SD Card path in the code:

 // SDCard Path
final String MEDIA_PATH = Environment.getExternalStorageDirectory().getPath() + "/";

I have probably tried about a dozen different alternatives to this, but obviously haven't found the correct path yet. When I set a breakpoint to see what the MEDIA_PATH variable contains, I see this:

MEDIA_PATH = "/storage/emulated/0/"

This does not seem to be where my music is located, because my music is on an SD Card. I would like to get the app to find the music on the SD card.

Here is the entire SongsManager.Java

package com.joshbgold.simplemusicplayer;

import android.os.Environment;

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

public class SongsManager {

    // SDCard Path
    final String MEDIA_PATH = Environment.getExternalStorageDirectory().getPath() + "/";
    private ArrayList<HashMap<String, String>> songsList = new ArrayList<HashMap<String, String>>();

    // Constructor
    public SongsManager(){
    }

    /**
     * Function to read all mp3 files from sdcard
     * and store the details in ArrayList
     * */
    public ArrayList<HashMap<String, String>> getPlayList(){
        File home = new File(MEDIA_PATH);

        if (home.listFiles(new FileExtensionFilter()).length > 0) {
            for (File file : home.listFiles(new FileExtensionFilter())) {
                HashMap<String, String> song = new HashMap<String, String>();
                song.put("songTitle", file.getName().substring(0, (file.getName().length() - 4)));
                song.put("songPath", file.getPath());

                // Adding each song to SongList
                songsList.add(song);
            }
        }
        // return songs playlist_item array
        return songsList;
    }

    /**
     * Class to filter files which are having .mp3 extension
     * */
    class FileExtensionFilter implements FilenameFilter {
        public boolean accept(File dir, String name) {
            return (name.endsWith(".mp3") || name.endsWith(".MP3") || name.endsWith(".wma"));
        }
    }
}

I have permissions to read and write to external storage listed in the manifest. I am targeting SDK 22 so that I do not have to implement the new style Marshmellow permissions just yet.

Full project is available for download on github as well: https://github.com/jogold9/Simple_Music_Player

joshgoldeneagle
  • 4,616
  • 2
  • 23
  • 30
  • Android mostly will tel you the path to your removable micro SD card. So you have to ask the user to indicate that path with a directory picker. Which Android version is in use? Have a look at getExternalFilesDirs(). Do you get two? Then you are in luck. – greenapps Jan 22 '16 at 22:14
  • The phone I am testing on runs Android 4.2 Jellybean. My understanding is that getExternalFilesDirs() is a method to gets the path on the SD Card specifically for your app, while GetExternalStorageDirectory() goes to root of SD card, which makes more sense for my case. See http://stackoverflow.com/questions/10123812/diff-between-getexternalfilesdir-and-getexternalstoragedirectory – joshgoldeneagle Jan 22 '16 at 22:37
  • But maybe like you said, I can add code to let the user indicate path with a directory picker. Can you direct me to any good example(s) of a directory picker for Android? – joshgoldeneagle Jan 22 '16 at 22:53
  • have you check permission code before reading the directory? maybe this answer woudl help you http://stackoverflow.com/a/34866073/2652524 – Gujarat Santana Jan 23 '16 at 06:20
  • @GujaratSantana I am targetting SDK 22 for now to avoid the new style permissions complexiities for now, as this app is just for me. My understanding is only starting with SDK 23 do you have to use the new style of permissions. – joshgoldeneagle Jan 23 '16 at 06:38
  • The many different ways external storage can work (and not work) in Android is a little insane: https://commonsware.com/blog/2014/04/09/storage-situation-removable-storage.html Nevertheless maybe I can use the MediaStore object in my case: http://developer.android.com/reference/android/provider/MediaStore.html – joshgoldeneagle Jan 23 '16 at 06:53
  • @joshgoldeneagle in my experience, to read external storage yes you have to use the style of the permission otherwise it won't work. as the android marsmalow or lollpop version, if you're targeting lower version you don't have to do that, CMIW. – Gujarat Santana Jan 23 '16 at 07:01
  • There is getExternalFilesDir() and there is getExternalFilesDirs(). The first delivers one path. The second can deliver more. If the second delivers more paths then the second path could indicate your micro sd card. Indeed app specific memory. But what does it matter? Just take the root of that path. – greenapps Jan 23 '16 at 08:56
  • `to avoid the new style permissions complexiities`. Dont let you scare off. It is really very simple all. – greenapps Jan 23 '16 at 08:58
  • @greenapps getExternalFilesDir() is actually not returning the root of the SD Card in my case. It is returning a portion of internal storage reserved for "external" storage. We can thank Google for this confusion. Anyhow, I will try getExternalFilesDirs(), Directory Picker, and/or using MediaStore object on Monday, to see if I can find anything that can see the audio files I have on the SD card. – joshgoldeneagle Jan 23 '16 at 23:53
  • `getExternalFilesDir() is actually not returning the root of the SD Card in my case.`. That is never the case. But it came often true for Android version 2 and so. – greenapps Jan 24 '16 at 08:38

1 Answers1

0

First, I found that the path for the SD card I added to the phone is not /sdcard/ but rather /storage/extSdCard/. Only /storage/extSdCard/ has music files on it. Both paths, however, are valid paths on my phone. I suspect that /sdcard/ is the internal storage for the phone.

The method getExternalStorageDirectory was actually returning a location on the internal SD Card, which is not what I was aiming for.

Second, I found some code that allows the user to browse the folder structure and select a folder. I customized the code to my liking and after some trial and error, got it to work for me.

FileDialog.java:

public class FileDialog extends ListActivity {

    private static final String ITEM_KEY = "key";
    private static final String ITEM_IMAGE = "image";
    private static final String ROOT = "/";
    public static final String START_PATH = "START_PATH";
    public static final String FORMAT_FILTER = "FORMAT_FILTER";
    public static final String RESULT_PATH = "RESULT_PATH";
    public static final String SELECTION_MODE = "SELECTION_MODE";
    public static final String CAN_SELECT_DIR = "CAN_SELECT_DIR";

    private List<String> path = null;
    private TextView myPath;
    private EditText mFileName;
    private ArrayList<HashMap<String, Object>> mList;

    private Button selectButton;

    private LinearLayout layoutSelect;
    private LinearLayout layoutCreate;
    private InputMethodManager inputManager;
    private String parentPath;
    private String currentPath = ROOT;
    private String musicFolderPath = "";

    private int selectionMode = SelectionMode.MODE_CREATE;

    private String[] formatFilter = null;

    private boolean canSelectDir = false;

    private File selectedFile;
    private HashMap<String, Integer> lastPositions = new HashMap<String, Integer>();

    /**
     * Sets all inputs and views
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setResult(RESULT_CANCELED, getIntent());

        setContentView(R.layout.file_dialog_main);

        ActionBar bar = getActionBar();
        if (bar != null) {
            bar.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#3F51B5")));  //sets action bar to color primary dark
        }

        myPath = (TextView) findViewById(R.id.path);
        mFileName = (EditText) findViewById(R.id.fdEditTextFile);

        inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);

        selectButton = (Button) findViewById(R.id.fdButtonSelect);
        selectButton.setEnabled(false);
        selectButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (selectedFile != null) {
                    musicFolderPath = selectedFile.getPath();
                    getIntent().putExtra(RESULT_PATH, musicFolderPath);
                    setResult(RESULT_OK, getIntent());
                    savePrefs("folder", musicFolderPath);
                    Toast.makeText(getApplicationContext(), "You select " + selectedFile.getPath(), Toast.LENGTH_LONG).show();
                    finish();
                }
            }
        });


        selectionMode = getIntent().getIntExtra(SELECTION_MODE, SelectionMode.MODE_CREATE);

        formatFilter = getIntent().getStringArrayExtra(FORMAT_FILTER);

        canSelectDir = getIntent().getBooleanExtra(CAN_SELECT_DIR, false);

        if (selectionMode == SelectionMode.MODE_OPEN) {
            //newButton.setEnabled(false);
        }

        layoutSelect = (LinearLayout) findViewById(R.id.fdLinearLayoutSelect);
        layoutCreate = (LinearLayout) findViewById(R.id.fdLinearLayoutCreate);
        layoutCreate.setVisibility(View.GONE);

        final Button cancelButton = (Button) findViewById(R.id.fdButtonCancel);
        cancelButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View view) {
                setSelectVisible(view);
                finish();
            }

        });

        String startPath = getIntent().getStringExtra(START_PATH);
        startPath = startPath != null ? startPath : ROOT;
        if (canSelectDir) {
            File file = new File(startPath);
            selectedFile = file;
            selectButton.setEnabled(true);
        }
        getDir(startPath);
    }

    private void getDir(String dirPath) {

        boolean useAutoSelection = dirPath.length() < currentPath.length();

        Integer position = lastPositions.get(parentPath);

        getDirImpl(dirPath);

        if (position != null && useAutoSelection) {
            getListView().setSelection(position);
        }

    }

    /**
     * Assembles the structure of files and directories of children provided directory.
     */
    private void getDirImpl(final String dirPath) {

        currentPath = dirPath;

        final List<String> item = new ArrayList<String>();
        path = new ArrayList<String>();
        mList = new ArrayList<HashMap<String, Object>>();

        File myFile = new File(currentPath);
        File[] files = myFile.listFiles();
        if (files == null) {
            currentPath = ROOT;
            myFile = new File(currentPath);
            files = myFile.listFiles();
        }
        myPath.setText(getText(R.string.location) + ": " + currentPath);

        if (!currentPath.equals(ROOT)) {

            item.add(ROOT);
            addItem(ROOT, R.drawable.folder);
            path.add(ROOT);

            item.add("../");
            addItem("../", R.drawable.folder);
            path.add(myFile.getParent());
            parentPath = myFile.getParent();

        }

        TreeMap<String, String> dirsMap = new TreeMap<String, String>();
        TreeMap<String, String> dirsPathMap = new TreeMap<String, String>();
        TreeMap<String, String> filesMap = new TreeMap<String, String>();
        TreeMap<String, String> filesPathMap = new TreeMap<String, String>();
        for (File file : files) {
            if (file.isDirectory()) {
                String dirName = file.getName();
                dirsMap.put(dirName, dirName);
                dirsPathMap.put(dirName, file.getPath());
            } else {
                final String fileName = file.getName();
                final String fileNameLwr = fileName.toLowerCase();

                if (formatFilter != null) {
                    boolean contains = false;
                    for (int i = 0; i < formatFilter.length; i++) {
                        final String formatLower = formatFilter[i].toLowerCase();
                        if (fileNameLwr.endsWith(formatLower)) {
                            contains = true;
                            break;
                        }
                    }
                    if (contains) {
                        filesMap.put(fileName, fileName);
                        filesPathMap.put(fileName, file.getPath());
                    }
                } else {
                    filesMap.put(fileName, fileName);
                    filesPathMap.put(fileName, file.getPath());
                }
            }
        }
        item.addAll(dirsMap.tailMap("").values());
        item.addAll(filesMap.tailMap("").values());
        path.addAll(dirsPathMap.tailMap("").values());
        path.addAll(filesPathMap.tailMap("").values());

        SimpleAdapter fileList = new SimpleAdapter(this, mList, R.layout.file_dialog_row, new String[] {
                ITEM_KEY, ITEM_IMAGE }, new int[] { R.id.fdrowtext, R.id.fdrowimage });

        for (String dir : dirsMap.tailMap("").values()) {
            addItem(dir, R.drawable.folder);
        }

        for (String file : filesMap.tailMap("").values()) {
            addItem(file, R.drawable.file);
        }

        fileList.notifyDataSetChanged();

        setListAdapter(fileList);

    }

    private void addItem(String fileName, int imageId) {
        HashMap<String, Object> item = new HashMap<String, Object>();
        item.put(ITEM_KEY, fileName);
        item.put(ITEM_IMAGE, imageId);
        mList.add(item);
    }

    /**
     * When a list item is clicked 1) If it is a directory, open
     * children; 2) If you can choose directory, define it as the
     * path chosen. 3) If file, defined as the path chosen. 4) Active button for selection.
     */
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {

        File file = new File(path.get(position));

        setSelectVisible(v);

        if (file.isDirectory()) {
            selectButton.setEnabled(false);
            if (file.canRead()) {
                lastPositions.put(currentPath, position);
                getDir(path.get(position));
                if (canSelectDir) {
                    selectedFile = file;
                    v.setSelected(true);
                    selectButton.setEnabled(true);
                }
            } else {
                new AlertDialog.Builder(this).setIcon(R.drawable.icon)
                        .setTitle("[" + file.getName() + "] " + getText(R.string.cant_read_folder))
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        }).show();
            }
        } else {
            selectedFile = file;
            v.setSelected(true);
            selectButton.setEnabled(true);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK)) {
            selectButton.setEnabled(false);

            if (layoutCreate.getVisibility() == View.VISIBLE) {
                layoutCreate.setVisibility(View.GONE);
                layoutSelect.setVisibility(View.VISIBLE);
            } else {
                if (!currentPath.equals(ROOT)) {
                    getDir(parentPath);
                } else {
                    return super.onKeyDown(keyCode, event);
                }
            }

            return true;
        } else {
            return super.onKeyDown(keyCode, event);
        }
    }

    /**
     * Sets the CREATE button and visibility
     */
    private void setCreateVisible(View v) {
        layoutCreate.setVisibility(View.VISIBLE);
        layoutSelect.setVisibility(View.GONE);

        inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
        selectButton.setEnabled(false);
    }

    /**
     * Sets the button SELECT and visibility
     */
    private void setSelectVisible(View v) {
        layoutCreate.setVisibility(View.GONE);
        layoutSelect.setVisibility(View.VISIBLE);

        inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
        selectButton.setEnabled(false);
    }

    //save prefs
    public void savePrefs(String key, String value){
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString(key, value);
        editor.apply();
    }
}

file_dialog_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/relativeLayout01"
                xmlns:android="http://schemas.android.com/apk/res/android"
                android:orientation="vertical" android:layout_width="fill_parent"
                android:layout_height="fill_parent">

    <LinearLayout android:id="@+id/fdLinearLayoutList"
                  android:orientation="vertical" android:layout_width="fill_parent"
                  android:layout_height="wrap_content" android:layout_alignParentBottom="true">

        <LinearLayout android:id="@+id/fdLinearLayoutSelect"
                      android:orientation="vertical" android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_alignParentBottom="true"  android:paddingBottom="5dp">

            <LinearLayout android:orientation="horizontal"
                          android:layout_width="fill_parent" android:layout_height="fill_parent"
                          android:layout_marginTop="5dp">
                <Button android:id="@+id/fdButtonCancel" android:layout_height="wrap_content"
                        android:layout_width="0dip" android:layout_weight="1"
                        android:background="@color/colorPrimaryDark"
                        android:text="@string/cancel"
                        android:layout_marginRight="5dp"></Button>

                <Button android:id="@+id/fdButtonSelect" android:layout_height="wrap_content"
                        android:layout_width="0dip" android:layout_weight="1"
                        android:background="@color/colorPrimaryDark"
                        android:text="@string/select"></Button>
            </LinearLayout>


        </LinearLayout>



        <LinearLayout android:id="@+id/fdLinearLayoutCreate"
                      android:orientation="vertical" android:layout_width="fill_parent"
                      android:layout_height="wrap_content"
                      android:layout_alignParentBottom="true" android:paddingLeft="10dp"
                      android:paddingRight="10dp" android:paddingBottom="5dp">
            <TextView android:id="@+id/textViewFilename" android:text="@string/file_name"
                      android:layout_width="fill_parent" android:layout_height="wrap_content" />
            <EditText android:text="" android:id="@+id/fdEditTextFile"
                      android:layout_width="fill_parent" android:layout_height="wrap_content"></EditText>


        </LinearLayout>

    </LinearLayout>

    <LinearLayout android:orientation="vertical"
                  android:layout_width="fill_parent" android:layout_height="fill_parent"
                  android:layout_above="@+id/fdLinearLayoutList">
        <TextView android:id="@+id/path" android:layout_width="fill_parent"
                  android:layout_height="wrap_content" />
        <ListView android:id="@android:id/list" android:layout_width="fill_parent"
                  android:layout_height="fill_parent" />
        <TextView android:id="@android:id/empty"
                  android:layout_width="fill_parent" android:layout_height="fill_parent"
                  android:text="@string/no_data" />
    </LinearLayout>
</RelativeLayout>

file_dialog_row.xml:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/list_selector">

    <ImageView
        android:id="@+id/fdrowimage"
        android:layout_width="wrap_content"
        android:layout_height="35dp"
        android:layout_alignParentLeft="true"
        android:paddingLeft="3dp"
        android:paddingRight="5dp"/>

    <TextView
        android:id="@+id/fdrowtext"
        android:layout_width="wrap_content"
        android:layout_height="65dp"
        android:layout_alignBottom="@+id/fdrowimage"
        android:layout_alignTop="@+id/fdrowimage"
        android:layout_toRightOf="@+id/fdrowimage"
        android:gravity="center_vertical"
        android:text="@+id/fdrowtext"
        android:textSize="23sp"/>

</RelativeLayout>
joshgoldeneagle
  • 4,616
  • 2
  • 23
  • 30