package org.music.player; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Environment; import android.os.FileObserver; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import java.util.regex.Pattern; import org.music.player.R; /** * A list adapter that provides a view of the filesystem. The active directory * is set through a {@link Limiter} and rows are displayed using MediaViews. */ public class FileSystemAdapter extends BaseAdapter implements LibraryAdapter , View.OnClickListener { private static final Pattern SPACE_SPLIT = Pattern.compile("\\s+"); private static final Pattern FILE_SEPARATOR = Pattern.compile(File.separator); /** * The owner LibraryActivity. */ final LibraryActivity mActivity; /** * A LayoutInflater to use. */ private final LayoutInflater mInflater; /** * The currently active limiter, set by a row expander being clicked. */ private Limiter mLimiter; /** * The files and folders in the current directory. */ private File[] mFiles; /** * The folder icon shown for folder rows. */ private final Drawable mFolderIcon; /** * The currently active filter, entered by the user from the search box. */ String[] mFilter; /** * Excludes dot files and files not matching mFilter. */ private final FilenameFilter mFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.charAt(0) == '.') return false; if (mFilter != null) { filename = filename.toLowerCase(); for (String term : mFilter) { if (!filename.contains(term)) return false; } } return true; } }; /** * Sorts folders before files first, then sorts alphabetically by name. */ private final Comparator<File> mFileComparator = new Comparator<File>() { @Override public int compare(File a, File b) { boolean aIsFolder = a.isDirectory(); boolean bIsFolder = b.isDirectory(); if (bIsFolder == aIsFolder) { return a.getName().compareToIgnoreCase(b.getName()); } else if (bIsFolder) { return 1; } return -1; } }; /** * The Observer instance for the current directory. */ private Observer mFileObserver; /** * Create a FileSystemAdapter. * * @param activity The LibraryActivity that will contain this adapter. * Called on to requery this adapter when the contents of the directory * change. * @param limiter An initial limiter to set. If none is given, will be set * to the external storage directory. */ public FileSystemAdapter(LibraryActivity activity, Limiter limiter) { mActivity = activity; mLimiter = limiter; mFolderIcon = activity.getResources().getDrawable(R.drawable.folder); mInflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (limiter == null) { limiter = buildLimiter(Environment.getExternalStorageDirectory()); } setLimiter(limiter); } @Override public Object query() { File file = mLimiter == null ? new File("/") : (File)mLimiter.data; if (mFileObserver == null) { mFileObserver = new Observer(file.getPath()); } File[] files = file.listFiles(mFileFilter); if (files != null) Arrays.sort(files, mFileComparator); return files; } @Override public void commitQuery(Object data) { mFiles = (File[])data; notifyDataSetInvalidated(); } @Override public void clear() { mFiles = null; notifyDataSetInvalidated(); } @Override public int getCount() { if (mFiles == null) return 0; return mFiles.length; } @Override public Object getItem(int pos) { return mFiles[pos]; } @Override public long getItemId(int pos) { return pos; } private static class ViewHolder { public int id; public TextView text; public View divider; public ImageView arrow; } @Override public View getView(int pos, View convertView, ViewGroup parent) { View view; ViewHolder holder; if (convertView == null) { view = mInflater.inflate(R.layout.library_row_expandable, null); holder = new ViewHolder(); holder.text = (TextView)view.findViewById(R.id.text); holder.divider = view.findViewById(R.id.divider); holder.arrow = (ImageView)view.findViewById(R.id.arrow); holder.text.setOnClickListener(this); holder.arrow.setOnClickListener(this); view.setTag(holder); } else { view = convertView; holder = (ViewHolder)view.getTag(); } File file = mFiles[pos]; boolean isDirectory = file.isDirectory(); holder.id = pos; holder.text.setText(file.getName()); holder.divider.setVisibility(isDirectory ? View.VISIBLE : View.GONE); holder.arrow.setVisibility(isDirectory ? View.VISIBLE : View.GONE); holder.text.setCompoundDrawablesWithIntrinsicBounds(isDirectory ? mFolderIcon : null, null, null, null); return view; } @Override public void setFilter(String filter) { if (filter == null) mFilter = null; else mFilter = SPACE_SPLIT.split(filter.toLowerCase()); } @Override public void setLimiter(Limiter limiter) { if (mFileObserver != null) mFileObserver.stopWatching(); mFileObserver = null; mLimiter = limiter; } @Override public Limiter getLimiter() { return mLimiter; } /** * Builds a limiter from the given folder. Only files contained in the * given folder will be shown if the limiter is set on this adapter. * * @param file A File pointing to a folder. * @return A limiter describing the given folder. */ public static Limiter buildLimiter(File file) { String[] fields = FILE_SEPARATOR.split(file.getPath().substring(1)); return new Limiter(MediaUtils.TYPE_FILE, fields, file); } @Override public Limiter buildLimiter(long id) { return buildLimiter(mFiles[(int)id]); } @Override public int getMediaType() { return MediaUtils.TYPE_FILE; } /** * FileObserver that reloads the files in this adapter. */ private class Observer extends FileObserver { public Observer(String path) { super(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_TO | FileObserver.MOVED_FROM); startWatching(); } @Override public void onEvent(int event, String path) { mActivity.mPagerAdapter.postRequestRequery(FileSystemAdapter.this); } } @Override public Intent createData(View view) { ViewHolder holder = (ViewHolder)view.getTag(); File file = mFiles[holder.id]; Intent intent = new Intent(); intent.putExtra(LibraryAdapter.DATA_TYPE, MediaUtils.TYPE_FILE); intent.putExtra(LibraryAdapter.DATA_ID, (long)holder.id); intent.putExtra(LibraryAdapter.DATA_TITLE, holder.text.getText().toString()); intent.putExtra(LibraryAdapter.DATA_EXPANDABLE, file.isDirectory()); String path; try { path = file.getCanonicalPath(); } catch (IOException e) { path = file.getAbsolutePath(); Log.e("VanillaMusic", "Failed to canonicalize path", e); } intent.putExtra(LibraryAdapter.DATA_FILE, path); return intent; } @Override public void onClick(View view) { Intent intent = createData((View)view.getParent()); if (view.getId() == R.id.arrow) { mActivity.onItemExpanded(intent); } else { mActivity.onItemClicked(intent); } } }