diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6cbd88b1..d1473edd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -152,6 +152,8 @@ THE SOFTWARE.
android:name="TabOrderActivity" />
+
diff --git a/orig/file_document.svgz b/orig/file_document.svgz
new file mode 100644
index 00000000..c31d7490
Binary files /dev/null and b/orig/file_document.svgz differ
diff --git a/orig/file_image.svgz b/orig/file_image.svgz
new file mode 100644
index 00000000..0622ea83
Binary files /dev/null and b/orig/file_image.svgz differ
diff --git a/orig/file_music.svgz b/orig/file_music.svgz
new file mode 100644
index 00000000..1686008d
Binary files /dev/null and b/orig/file_music.svgz differ
diff --git a/orig/folder.svgz b/orig/folder.svgz
index 5622bf26..8a7b8d42 100644
Binary files a/orig/folder.svgz and b/orig/folder.svgz differ
diff --git a/res/drawable-hdpi/file_document.png b/res/drawable-hdpi/file_document.png
new file mode 100644
index 00000000..a74b647a
Binary files /dev/null and b/res/drawable-hdpi/file_document.png differ
diff --git a/res/drawable-hdpi/file_image.png b/res/drawable-hdpi/file_image.png
new file mode 100644
index 00000000..1f2b0408
Binary files /dev/null and b/res/drawable-hdpi/file_image.png differ
diff --git a/res/drawable-hdpi/file_music.png b/res/drawable-hdpi/file_music.png
new file mode 100644
index 00000000..47f77bd7
Binary files /dev/null and b/res/drawable-hdpi/file_music.png differ
diff --git a/res/drawable-hdpi/folder.png b/res/drawable-hdpi/folder.png
index 77d7e869..b2034e80 100644
Binary files a/res/drawable-hdpi/folder.png and b/res/drawable-hdpi/folder.png differ
diff --git a/res/drawable-mdpi/file_document.png b/res/drawable-mdpi/file_document.png
new file mode 100644
index 00000000..b055c54d
Binary files /dev/null and b/res/drawable-mdpi/file_document.png differ
diff --git a/res/drawable-mdpi/file_image.png b/res/drawable-mdpi/file_image.png
new file mode 100644
index 00000000..a37bb03c
Binary files /dev/null and b/res/drawable-mdpi/file_image.png differ
diff --git a/res/drawable-mdpi/file_music.png b/res/drawable-mdpi/file_music.png
new file mode 100644
index 00000000..72201ddf
Binary files /dev/null and b/res/drawable-mdpi/file_music.png differ
diff --git a/res/drawable-mdpi/folder.png b/res/drawable-mdpi/folder.png
index 137b88cf..629ed014 100644
Binary files a/res/drawable-mdpi/folder.png and b/res/drawable-mdpi/folder.png differ
diff --git a/res/drawable-xhdpi/file_document.png b/res/drawable-xhdpi/file_document.png
new file mode 100644
index 00000000..37e62102
Binary files /dev/null and b/res/drawable-xhdpi/file_document.png differ
diff --git a/res/drawable-xhdpi/file_image.png b/res/drawable-xhdpi/file_image.png
new file mode 100644
index 00000000..d479164b
Binary files /dev/null and b/res/drawable-xhdpi/file_image.png differ
diff --git a/res/drawable-xhdpi/file_music.png b/res/drawable-xhdpi/file_music.png
new file mode 100644
index 00000000..4f018f99
Binary files /dev/null and b/res/drawable-xhdpi/file_music.png differ
diff --git a/res/drawable-xhdpi/folder.png b/res/drawable-xhdpi/folder.png
index 669b0ea5..ac5633cb 100644
Binary files a/res/drawable-xhdpi/folder.png and b/res/drawable-xhdpi/folder.png differ
diff --git a/res/drawable-xxhdpi/file_document.png b/res/drawable-xxhdpi/file_document.png
new file mode 100644
index 00000000..3174bb61
Binary files /dev/null and b/res/drawable-xxhdpi/file_document.png differ
diff --git a/res/drawable-xxhdpi/file_image.png b/res/drawable-xxhdpi/file_image.png
new file mode 100644
index 00000000..95d22fa4
Binary files /dev/null and b/res/drawable-xxhdpi/file_image.png differ
diff --git a/res/drawable-xxhdpi/file_music.png b/res/drawable-xxhdpi/file_music.png
new file mode 100644
index 00000000..7ce874fa
Binary files /dev/null and b/res/drawable-xxhdpi/file_music.png differ
diff --git a/res/drawable-xxhdpi/folder.png b/res/drawable-xxhdpi/folder.png
index 38676410..fb369492 100644
Binary files a/res/drawable-xxhdpi/folder.png and b/res/drawable-xxhdpi/folder.png differ
diff --git a/res/layout/filebrowser_content.xml b/res/layout/folderpicker_content.xml
similarity index 96%
rename from res/layout/filebrowser_content.xml
rename to res/layout/folderpicker_content.xml
index 970d2dab..7bf3ae09 100644
--- a/res/layout/filebrowser_content.xml
+++ b/res/layout/folderpicker_content.xml
@@ -44,7 +44,7 @@ THE SOFTWARE.
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
- android:text="@string/select" />
+ android:text="@string/empty" />
@@ -61,7 +61,7 @@ THE SOFTWARE.
android:layout_height="0px"
android:layout_width="fill_parent"
android:layout_weight="1"
- android:choiceMode="multipleChoice"
+ android:choiceMode="none"
dslv:drag_enabled="false" />
diff --git a/res/layout/medialibrary_preferences.xml b/res/layout/medialibrary_preferences.xml
index f6c47df9..664843f0 100644
--- a/res/layout/medialibrary_preferences.xml
+++ b/res/layout/medialibrary_preferences.xml
@@ -39,6 +39,25 @@ along with this program. If not, see .
android:layout_height="wrap_content"
android:text="@string/media_scan_force_bastp" />
+
+
+
+
+
+
Filebrowser home
Filebrowser starts at this directory
Select
+ Save
Automatic playlist creation
@@ -319,6 +320,7 @@ THE SOFTWARE.
No receiving apps found for this media type!
Media library
+ Indexed directories
Media scanner
Scan full filesystem (Slow)
Flush media database (Dangerzone!)
@@ -334,6 +336,11 @@ THE SOFTWARE.
Scanner options changed
Changing this option will start a full rescan of your library. Would you like to continue?
+
+ Long press to modify folder options
+ Include
+ Exclude
+ Neutral
Plugins
diff --git a/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java b/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java
index d6c7163c..53fd05be 100644
--- a/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java
+++ b/src/ch/blinkenlights/android/medialibrary/MediaLibrary.java
@@ -23,8 +23,14 @@ import android.database.Cursor;
import android.database.ContentObserver;
import android.provider.MediaStore;
import android.os.Environment;
+import android.util.Log;
import java.util.ArrayList;
+
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
import java.io.File;
public class MediaLibrary {
@@ -37,7 +43,6 @@ public class MediaLibrary {
public static final String TABLE_GENRES_SONGS = "genres_songs";
public static final String TABLE_PLAYLISTS = "playlists";
public static final String TABLE_PLAYLISTS_SONGS = "playlists_songs";
- public static final String TABLE_PREFERENCES = "preferences";
public static final String VIEW_ARTISTS = "_artists";
public static final String VIEW_ALBUMARTISTS = "_albumartists";
public static final String VIEW_COMPOSERS = "_composers";
@@ -50,17 +55,16 @@ public class MediaLibrary {
public static final int ROLE_COMPOSER = 1;
public static final int ROLE_ALBUMARTIST = 2;
- private static final String PREF_KEY_FORCE_BASTP = "force_bastp";
- private static final String PREF_KEY_GROUP_ALBUMS = "group_albums";
- private static final String PREF_KEY_NATIVE_LIBRARY_COUNT = "native_audio_db_count";
- private static final String PREF_KEY_NATIVE_LAST_MTIME = "native_last_mtime";
+ public static final String PREFERENCES_FILE = "_prefs-v1.obj";
/**
* Options used by the MediaScanner class
*/
- public static class Preferences {
+ public static class Preferences implements Serializable {
public boolean forceBastp;
public boolean groupAlbumsByFolder;
+ public ArrayList mediaFolders;
+ public ArrayList blacklistedFolders;
int _nativeLibraryCount;
int _nativeLastMtime;
}
@@ -121,17 +125,63 @@ public class MediaLibrary {
public static MediaLibrary.Preferences getPreferences(Context context) {
MediaLibrary.Preferences prefs = sPreferences;
if (prefs == null) {
- MediaLibraryBackend backend = getBackend(context);
- prefs = new MediaLibrary.Preferences();
- prefs.forceBastp = backend.getSetPreference(PREF_KEY_FORCE_BASTP, -1) != 0;
- prefs.groupAlbumsByFolder = backend.getSetPreference(PREF_KEY_GROUP_ALBUMS, -1) != 0;
- prefs._nativeLibraryCount = backend.getSetPreference(PREF_KEY_NATIVE_LIBRARY_COUNT, -1);
- prefs._nativeLastMtime = backend.getSetPreference(PREF_KEY_NATIVE_LAST_MTIME, -1);
+ try (ObjectInputStream ois = new ObjectInputStream(context.openFileInput(PREFERENCES_FILE))) {
+ prefs = (MediaLibrary.Preferences)ois.readObject();
+ } catch (Exception e) {
+ Log.w("VanillaMusic", "Returning default media-library preferences due to error: "+ e);
+ }
+
+ if (prefs == null)
+ prefs = new MediaLibrary.Preferences();
+
+ if (prefs.mediaFolders == null || prefs.mediaFolders.size() == 0)
+ prefs.mediaFolders = discoverDefaultMediaPaths();
+
+ if (prefs.blacklistedFolders == null) // we allow this to be empty, but it must not be null.
+ prefs.blacklistedFolders = discoverDefaultBlacklistedPaths();
+
sPreferences = prefs; // cached for frequent access
}
return prefs;
}
+ /**
+ * Returns the guessed media paths for this device
+ *
+ * @return array with guessed directories
+ */
+ private static ArrayList discoverDefaultMediaPaths() {
+ ArrayList defaultPaths = new ArrayList<>();
+
+ // this should always exist
+ defaultPaths.add(Environment.getExternalStorageDirectory().getAbsolutePath());
+
+ // this *may* exist
+ File sdCard = new File("/storage/sdcard1");
+ if (sdCard.isDirectory())
+ defaultPaths.add(sdCard.getAbsolutePath());
+ return defaultPaths;
+ }
+
+ /**
+ * Returns default paths which should be blacklisted
+ *
+ * @return array with guessed blacklist
+ */
+ private static ArrayList discoverDefaultBlacklistedPaths() {
+ final String[] defaultBlacklistPostfix = { "Android/data", "Alarms", "Notifications", "Ringtones" };
+ ArrayList defaultPaths = new ArrayList<>();
+
+ for (String path : discoverDefaultMediaPaths()) {
+ for (int i = 0; i < defaultBlacklistPostfix.length; i++) {
+ File guess = new File(path + "/" + defaultBlacklistPostfix[i]);
+ if (guess.isDirectory())
+ defaultPaths.add(guess.getAbsolutePath());
+ }
+ }
+ return defaultPaths;
+ }
+
/**
* Updates the scanner preferences
*
@@ -141,11 +191,14 @@ public class MediaLibrary {
*/
public static void setPreferences(Context context, MediaLibrary.Preferences prefs) {
MediaLibraryBackend backend = getBackend(context);
- backend.getSetPreference(PREF_KEY_FORCE_BASTP, prefs.forceBastp ? 1 : 0);
- backend.getSetPreference(PREF_KEY_GROUP_ALBUMS, prefs.groupAlbumsByFolder ? 1 : 0);
- backend.getSetPreference(PREF_KEY_NATIVE_LIBRARY_COUNT, prefs._nativeLibraryCount);
- backend.getSetPreference(PREF_KEY_NATIVE_LAST_MTIME, prefs._nativeLastMtime);
- sPreferences = null;
+
+ try (ObjectOutputStream oos = new ObjectOutputStream(context.openFileOutput(PREFERENCES_FILE, 0))) {
+ oos.writeObject(prefs);
+ } catch (Exception e) {
+ Log.w("VanillaMusic", "Failed to store media preferences: " + e);
+ }
+
+ sPreferences = prefs;
}
/**
@@ -456,25 +509,6 @@ public class MediaLibrary {
return (hash < 0 ? hash*-1 : hash);
}
- /**
- * Returns the guessed media paths for this device
- *
- * @return array with guessed directories
- */
- public static File[] discoverMediaPaths() {
- ArrayList scanTargets = new ArrayList<>();
-
- // this should always exist
- scanTargets.add(Environment.getExternalStorageDirectory());
-
- // this *may* exist
- File sdCard = new File("/storage/sdcard1");
- if (sdCard.isDirectory())
- scanTargets.add(sdCard);
-
- return scanTargets.toArray(new File[scanTargets.size()]);
- }
-
// Columns of Song entries
public interface SongColumns {
/**
diff --git a/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java b/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java
index a6998d02..ae97a118 100644
--- a/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java
+++ b/src/ch/blinkenlights/android/medialibrary/MediaLibraryBackend.java
@@ -35,7 +35,7 @@ public class MediaLibraryBackend extends SQLiteOpenHelper {
/**
* The database version we are using
*/
- private static final int DATABASE_VERSION = 20170217;
+ private static final int DATABASE_VERSION = 20170407;
/**
* on-disk file to store the database
*/
@@ -104,33 +104,6 @@ public class MediaLibraryBackend extends SQLiteOpenHelper {
return mtime;
}
- /**
- * Simple interface to set and get preference values
- *
- * @param stringKey the key to use
- * @param newVal the value to set
- *
- * Note: The new value will only be set if it is >= 0
- * Lookup failures will return 0
- */
- int getSetPreference(String stringKey, int newVal) {
- int oldVal = 0; // this is returned if we found nothing
- int key = Math.abs(stringKey.hashCode());
- SQLiteDatabase dbh = getWritableDatabase();
-
- Cursor cursor = dbh.query(MediaLibrary.TABLE_PREFERENCES, new String[] { MediaLibrary.PreferenceColumns.VALUE }, MediaLibrary.PreferenceColumns.KEY+"="+key, null, null, null, null, null);
- if (cursor.moveToFirst()) {
- oldVal = cursor.getInt(0);
- }
- cursor.close();
-
- if (newVal >= 0 && newVal != oldVal) {
- dbh.execSQL("INSERT OR REPLACE INTO "+MediaLibrary.TABLE_PREFERENCES+" ("+MediaLibrary.PreferenceColumns.KEY+", "+MediaLibrary.PreferenceColumns.VALUE+") "
- +" VALUES("+key+", "+newVal+")");
- }
- return oldVal;
- }
-
/**
* Wrapper for SQLiteDatabse.delete() function
*
diff --git a/src/ch/blinkenlights/android/medialibrary/MediaScanner.java b/src/ch/blinkenlights/android/medialibrary/MediaScanner.java
index e8cc3781..72ce47bd 100644
--- a/src/ch/blinkenlights/android/medialibrary/MediaScanner.java
+++ b/src/ch/blinkenlights/android/medialibrary/MediaScanner.java
@@ -108,8 +108,9 @@ public class MediaScanner implements Handler.Callback {
* Performs a 'slow' scan by inspecting all files on the device
*/
public void startFullScan() {
- for (File dir : MediaLibrary.discoverMediaPaths()) {
- mScanPlan.addNextStep(RPC_READ_DIR, dir);
+ MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(mContext);
+ for (String path : prefs.mediaFolders) {
+ mScanPlan.addNextStep(RPC_READ_DIR, new File(path));
}
mScanPlan.addNextStep(RPC_LIBRARY_VRFY, null);
mScanPlan.addNextStep(RPC_NATIVE_VRFY, null);
@@ -559,7 +560,6 @@ public class MediaScanner implements Handler.Callback {
}
private static final Pattern sIgnoredFilenames = Pattern.compile("^([^\\.]+|.+\\.(jpe?g|gif|png|bmp|webm|txt|pdf|avi|mp4|mkv|zip|tgz|xml))$", Pattern.CASE_INSENSITIVE);
- private static final Pattern sIgnoredDirectories = Pattern.compile("^.+/(Android/data|Alarms|Notifications|Ringtones)/.+$", Pattern.CASE_INSENSITIVE);
/**
* Returns true if the file should not be scanned
*
@@ -567,8 +567,30 @@ public class MediaScanner implements Handler.Callback {
* @return boolean
*/
private boolean isBlacklisted(File file) {
- boolean blacklisted = sIgnoredFilenames.matcher(file.getName()).matches() || sIgnoredDirectories.matcher(file.getPath()).matches();
- return blacklisted;
+ if (sIgnoredFilenames.matcher(file.getName()).matches())
+ return true;
+
+ int wlPoints = -1;
+ int blPoints = -1;
+
+ for (String path : MediaLibrary.getPreferences(mContext).mediaFolders) {
+ if (path.length() > wlPoints &&
+ file.getPath().startsWith(path)) {
+ wlPoints = path.length();
+ }
+ }
+
+ for (String path : MediaLibrary.getPreferences(mContext).blacklistedFolders) {
+ if (path.length() > blPoints &&
+ file.getPath().startsWith(path)) {
+ blPoints = path.length();
+ }
+ }
+
+ // Consider a file to be blacklisted if it is not
+ // present in any whitelisted dir OR if we found
+ // a blacklist entry with a longer prefix.
+ return (wlPoints < 0 || blPoints > wlPoints);
}
diff --git a/src/ch/blinkenlights/android/medialibrary/MediaSchema.java b/src/ch/blinkenlights/android/medialibrary/MediaSchema.java
index 91d6ed8f..934f45bf 100644
--- a/src/ch/blinkenlights/android/medialibrary/MediaSchema.java
+++ b/src/ch/blinkenlights/android/medialibrary/MediaSchema.java
@@ -116,13 +116,6 @@ public class MediaSchema {
+ MediaLibrary.PlaylistSongColumns.POSITION +" INTEGER NOT NULL "
+ ");";
- /**
- * SQL schema for our preferences
- */
- private static final String DATABASE_CREATE_PREFERENCES = "CREATE TABLE "+ MediaLibrary.TABLE_PREFERENCES + " ("
- + MediaLibrary.PreferenceColumns.KEY +" INTEGER PRIMARY KEY, "
- + MediaLibrary.PreferenceColumns.VALUE +" INTEGER NOT NULL "
- + ");";
/**
* Index to select a playlist quickly
*/
@@ -263,7 +256,6 @@ public class MediaSchema {
dbh.execSQL(VIEW_CREATE_ALBUMARTISTS);
dbh.execSQL(VIEW_CREATE_COMPOSERS);
dbh.execSQL(VIEW_CREATE_PLAYLIST_SONGS);
- dbh.execSQL(DATABASE_CREATE_PREFERENCES);
}
/**
@@ -286,11 +278,6 @@ public class MediaSchema {
dbh.execSQL("UPDATE songs SET disc_num=1 WHERE disc_num IS null");
}
- if (oldVersion < 20170120) {
- dbh.execSQL(DATABASE_CREATE_PREFERENCES);
- triggerFullMediaScan(dbh);
- }
-
if (oldVersion < 20170211) {
// older versions of triggerFullMediaScan did this by mistake
dbh.execSQL("UPDATE songs SET mtime=1 WHERE mtime=0");
@@ -302,18 +289,10 @@ public class MediaSchema {
dbh.execSQL(VIEW_CREATE_SONGS_ALBUMS_ARTISTS_HUGE);
}
- }
+ if (oldVersion >= 20170120 && oldVersion < 20170407) {
+ dbh.execSQL("DROP TABLE preferences");
+ }
- /**
- * Changes the mtime of all songs and flushes the scanner progress / preferences
- * This triggers a full rebuild of the library on startup
- *
- * @param dbh the writeable dbh to use
- */
- private static void triggerFullMediaScan(SQLiteDatabase dbh) {
- dbh.execSQL("UPDATE "+MediaLibrary.TABLE_SONGS+" SET "+MediaLibrary.SongColumns.MTIME+"=1");
- // wipes non-bools only - not nice but good enough for now
- dbh.execSQL("DELETE FROM "+MediaLibrary.TABLE_PREFERENCES+" WHERE "+MediaLibrary.PreferenceColumns.VALUE+" > 1");
}
}
diff --git a/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java b/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java
index 426b14bc..e0ce49ac 100644
--- a/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java
+++ b/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java
@@ -48,6 +48,8 @@ public class FileSystemAdapter
{
private static final Pattern SPACE_SPLIT = Pattern.compile("\\s+");
private static final Pattern FILE_SEPARATOR = Pattern.compile(File.separator);
+ private static final Pattern GUESS_MUSIC = Pattern.compile("^([^\\.]+|.+\\.(mp3|ogg|mka|opus|flac|aac|m4a|wav))$", Pattern.CASE_INSENSITIVE);
+ private static final Pattern GUESS_IMAGE = Pattern.compile("^([^\\.]+|.+\\.(gif|jpe?g|png|bmp|tiff?))$", Pattern.CASE_INSENSITIVE);
/**
* Sort by filename.
@@ -243,19 +245,17 @@ public class FileSystemAdapter
holder = new ViewHolder();
row.setTag(holder);
- row.getCoverView().setImageDrawable(mFolderIcon);
} else {
row = (DraggableRow)convertView;
holder = (ViewHolder)row.getTag();
}
- File file = mFiles[pos];
- boolean isDirectory = file.isDirectory();
holder.id = pos;
+ final File file = mFiles[pos];
row.getTextView().setText(file.getName());
- row.getCoverView().setVisibility(isDirectory ? View.VISIBLE : View.GONE);
- row.showDragger(isDirectory);
+ row.showDragger(file.isDirectory());
+ row.getCoverView().setImageDrawable(getDrawableForFile(file));
return row;
}
@@ -283,6 +283,25 @@ public class FileSystemAdapter
return mLimiter;
}
+ /**
+ * Returns a drawable for given file.
+ * This function is rather fast as the file type is guessed
+ * based on the extension.
+ *
+ * @return drawable for the guessed mime type
+ */
+ private Drawable getDrawableForFile(File file) {
+ int res = R.drawable.file_document;
+ if (file.isDirectory()) {
+ res = R.drawable.folder;
+ } else if (GUESS_MUSIC.matcher(file.getName()).matches()) {
+ res = R.drawable.file_music;
+ } else if (GUESS_IMAGE.matcher(file.getName()).matches()) {
+ res = R.drawable.file_image;
+ }
+ return mActivity.getResources().getDrawable(res);
+ }
+
/**
* Returns the unixpath represented by this limiter
*
diff --git a/src/ch/blinkenlights/android/vanilla/FilebrowserStartActivity.java b/src/ch/blinkenlights/android/vanilla/FilebrowserStartActivity.java
index fa0c3b31..4dba973d 100644
--- a/src/ch/blinkenlights/android/vanilla/FilebrowserStartActivity.java
+++ b/src/ch/blinkenlights/android/vanilla/FilebrowserStartActivity.java
@@ -22,6 +22,7 @@ import android.os.Bundle;
import android.content.SharedPreferences;
import java.io.File;
+import java.util.ArrayList;
public class FilebrowserStartActivity extends FolderPickerActivity {
@@ -30,6 +31,8 @@ public class FilebrowserStartActivity extends FolderPickerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setTitle(R.string.filebrowser_start);
+
mPrefEditor = PlaybackService.getSettings(this).edit();
// Make sure that we display the current selection
@@ -39,7 +42,7 @@ public class FilebrowserStartActivity extends FolderPickerActivity {
@Override
- public void onFolderSelected(File directory) {
+ public void onFolderPicked(File directory, ArrayList a, ArrayList b) {
mPrefEditor.putString(PrefKeys.FILESYSTEM_BROWSE_START, directory.getAbsolutePath());
mPrefEditor.commit();
finish();
diff --git a/src/ch/blinkenlights/android/vanilla/FolderPickerActivity.java b/src/ch/blinkenlights/android/vanilla/FolderPickerActivity.java
index 7fe470a4..9a65b236 100644
--- a/src/ch/blinkenlights/android/vanilla/FolderPickerActivity.java
+++ b/src/ch/blinkenlights/android/vanilla/FolderPickerActivity.java
@@ -18,8 +18,11 @@
package ch.blinkenlights.android.vanilla;
import java.util.Arrays;
+import java.util.ArrayList;
import java.io.File;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.MenuItem;
@@ -33,7 +36,8 @@ import android.widget.Toast;
import com.mobeta.android.dslv.DragSortListView;
public abstract class FolderPickerActivity extends Activity
- implements AdapterView.OnItemClickListener
+ implements AdapterView.OnItemClickListener,
+ AdapterView.OnItemLongClickListener
{
/**
@@ -56,14 +60,26 @@ public abstract class FolderPickerActivity extends Activity
* The array adapter of our listview
*/
private FolderPickerAdapter mListAdapter;
+ /**
+ * True if folder-tri-state selection mode
+ * is enabled
+ */
+ private boolean mTritastic;
+ /**
+ * List of included dirs in tristate mode
+ */
+ private ArrayList mIncludedDirs;
+ /**
+ * List of excluded dirs in tristate mode
+ */
+ private ArrayList mExcludedDirs;
@Override
public void onCreate(Bundle savedInstanceState) {
ThemeHelper.setTheme(this, R.style.BackActionBar);
super.onCreate(savedInstanceState);
- setTitle(R.string.filebrowser_start);
- setContentView(R.layout.filebrowser_content);
+ setContentView(R.layout.folderpicker_content);
mCurrentPath = new File("/");
mListAdapter = new FolderPickerAdapter(this, 0);
@@ -76,16 +92,39 @@ public abstract class FolderPickerActivity extends Activity
mSaveButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- onFolderSelected(mCurrentPath);
+ onFolderPicked(mCurrentPath, mIncludedDirs, mExcludedDirs);
}});
+ // init defaults
+ enableTritasticSelect(false, null, null);
}
/**
* Called after a folder was selected
*
* @param directory the selected directory
+ * @param included unique list of included directories in tristastic mode
+ * @param excluded unique list of excluded directories in triatastic mode
*/
- public abstract void onFolderSelected(File directory);
+ public abstract void onFolderPicked(File directory, ArrayList included, ArrayList excluded);
+
+ /**
+ * Enables tritastic selection, that is: user can select each
+ * directory to be in included, excluded or neutral state.
+ *
+ * @param enabled enables or disables this feature
+ * @param included initial list of included dirs
+ * @param excluded initial list of excluded dirs
+ */
+ public void enableTritasticSelect(boolean enabled, ArrayList included, ArrayList excluded) {
+ mTritastic = enabled;
+ mIncludedDirs = (enabled ? included : null);
+ mExcludedDirs = (enabled ? excluded : null);
+ mListView.setOnItemLongClickListener(enabled ? this : null);
+ mSaveButton.setText(enabled ? R.string.save : R.string.select);
+
+ if (enabled)
+ Toast.makeText(this, R.string.hint_long_press_to_modify_folder, Toast.LENGTH_SHORT).show();
+ }
/**
* Jumps to given directory
@@ -94,7 +133,7 @@ public abstract class FolderPickerActivity extends Activity
*/
void setCurrentDirectory(File directory) {
mCurrentPath = directory;
- refreshDirectoryList();
+ refreshDirectoryList(true);
}
/**
@@ -104,7 +143,7 @@ public abstract class FolderPickerActivity extends Activity
@Override
public void onResume() {
super.onResume();
- refreshDirectoryList();
+ refreshDirectoryList(false);
}
/**
@@ -131,35 +170,83 @@ public abstract class FolderPickerActivity extends Activity
*/
@Override
public void onItemClick(AdapterView> parent, View view, int pos, long id) {
- String dirent = mListAdapter.getItem(pos);
+ FolderPickerAdapter.Item item = mListAdapter.getItem(pos);
File newPath = null;
if(pos == 0) {
newPath = mCurrentPath.getParentFile();
}
else {
- newPath = new File(mCurrentPath, dirent);
+ newPath = new File(mCurrentPath, item.name);
}
if (newPath != null)
setCurrentDirectory(newPath);
}
+ /**
+ * Called on long-click on a row
+ */
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int pos, long id) {
+ FolderPickerAdapter.Item item = mListAdapter.getItem(pos);
+
+ if (item.file == null)
+ return false;
+
+ final String path = item.file.getAbsolutePath();
+ final CharSequence[] options = new CharSequence[]{
+ getResources().getString(R.string.folder_include),
+ getResources().getString(R.string.folder_exclude),
+ getResources().getString(R.string.folder_neutral)
+ };
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(item.name)
+ .setItems(options,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mIncludedDirs.remove(path);
+ mExcludedDirs.remove(path);
+ switch (which) {
+ case 0:
+ mIncludedDirs.add(path);
+ break;
+ case 1:
+ mExcludedDirs.add(path);
+ break;
+ default:
+ }
+ refreshDirectoryList(false);
+ }
+ });
+ builder.create().show();
+ return true;
+ }
+
/**
* display mCurrentPath in the dialog
*/
- private void refreshDirectoryList() {
+ private void refreshDirectoryList(boolean scroll) {
File path = mCurrentPath;
File[]dirs = path.listFiles();
mListAdapter.clear();
- mListAdapter.add("../");
+ mListAdapter.add(new FolderPickerAdapter.Item("../", null, 0));
if(dirs != null) {
Arrays.sort(dirs);
for(File fentry: dirs) {
if(fentry.isDirectory()) {
- mListAdapter.add(fentry.getName());
+ int color = 0;
+ if (mTritastic) {
+ if (mIncludedDirs.contains(fentry.getAbsolutePath()))
+ color = 0xff00c853;
+ if (mExcludedDirs.contains(fentry.getAbsolutePath()))
+ color = 0xffd50000;
+ }
+ FolderPickerAdapter.Item item = new FolderPickerAdapter.Item(fentry.getName(), fentry, color);
+ mListAdapter.add(item);
}
}
}
@@ -167,7 +254,8 @@ public abstract class FolderPickerActivity extends Activity
Toast.makeText(this, "Failed to display " + path.getAbsolutePath(), Toast.LENGTH_SHORT).show();
}
mPathDisplay.setText(path.getAbsolutePath());
- mListView.setSelectionFromTop(0, 0);
+ if (scroll)
+ mListView.setSelectionFromTop(0, 0);
}
}
diff --git a/src/ch/blinkenlights/android/vanilla/FolderPickerAdapter.java b/src/ch/blinkenlights/android/vanilla/FolderPickerAdapter.java
index 9998d2f5..2144d5f8 100644
--- a/src/ch/blinkenlights/android/vanilla/FolderPickerAdapter.java
+++ b/src/ch/blinkenlights/android/vanilla/FolderPickerAdapter.java
@@ -29,10 +29,23 @@ import android.widget.TextView;
import android.widget.ArrayAdapter;
import android.graphics.drawable.Drawable;
+import java.io.File;
+
public class FolderPickerAdapter
- extends ArrayAdapter
+ extends ArrayAdapter
{
-
+
+ public static class Item {
+ String name;
+ File file;
+ int color;
+ public Item(String name, File file, int color) {
+ this.name = name;
+ this.file = file;
+ this.color = color;
+ }
+ }
+
private final LayoutInflater mInflater;
public FolderPickerAdapter(Context context, int resource) {
@@ -47,15 +60,15 @@ public class FolderPickerAdapter
if (convertView == null) {
row = (DraggableRow)mInflater.inflate(R.layout.draggable_row, parent, false);
row.setupLayout(DraggableRow.LAYOUT_LISTVIEW);
-
row.getCoverView().setImageResource(R.drawable.folder);
} else {
row = (DraggableRow)convertView;
}
- String label = getItem(pos);
- row.getTextView().setText(label);
+ Item item = (Item)getItem(pos);
+ row.getTextView().setText(item.name);
+ row.getCoverView().setColorFilter(item.color);
return row;
}
diff --git a/src/ch/blinkenlights/android/vanilla/MediaFoldersSelectionActivity.java b/src/ch/blinkenlights/android/vanilla/MediaFoldersSelectionActivity.java
new file mode 100644
index 00000000..2e49df37
--- /dev/null
+++ b/src/ch/blinkenlights/android/vanilla/MediaFoldersSelectionActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 Adrian Ulrich
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package ch.blinkenlights.android.vanilla;
+
+import ch.blinkenlights.android.medialibrary.MediaLibrary;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.SharedPreferences;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class MediaFoldersSelectionActivity extends FolderPickerActivity {
+
+ private SharedPreferences.Editor mPrefEditor;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle(R.string.media_folders_header);
+
+ MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(this);
+ File startPath = FileUtils.getFilesystemBrowseStart(this);
+
+ // Make sure that we display the current selection
+ setCurrentDirectory(startPath);
+ enableTritasticSelect(true, prefs.mediaFolders, prefs.blacklistedFolders);
+ }
+
+
+ @Override
+ public void onFolderPicked(File directory, ArrayList included, ArrayList excluded) {
+ MediaLibrary.Preferences prefs = MediaLibrary.getPreferences(this);
+ prefs.mediaFolders = included;
+ prefs.blacklistedFolders = excluded;
+ MediaLibrary.setPreferences(this, prefs);
+ finish();
+ }
+
+}
diff --git a/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java b/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java
index 0b125c10..a87656fb 100644
--- a/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java
+++ b/src/ch/blinkenlights/android/vanilla/PreferencesMediaLibrary.java
@@ -23,6 +23,7 @@ import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -49,6 +50,10 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
* The cancel button
*/
private View mCancelButton;
+ /**
+ * The edit-media-folders button
+ */
+ private View mEditButton;
/**
* The debug / progress text describing the scan status
*/
@@ -65,6 +70,10 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
* The number of hours of music we have
*/
private TextView mStatsPlaytime;
+ /**
+ * A list of scanned media directories
+ */
+ private TextView mMediaDirectories;
/**
* Checkbox for full scan
*/
@@ -85,6 +94,10 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
* Set if we should start a full scan due to option changes
*/
private boolean mFullScanPending;
+ /**
+ * Set if we are in the edit dialog
+ */
+ private boolean mIsEditingDirectories;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -97,10 +110,12 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
mStartButton = (View)view.findViewById(R.id.start_button);
mCancelButton = (View)view.findViewById(R.id.cancel_button);
+ mEditButton = (View)view.findViewById(R.id.edit_button);
mProgressText = (TextView)view.findViewById(R.id.media_stats_progress_text);
mProgressBar = (ProgressBar)view.findViewById(R.id.media_stats_progress_bar);
mStatsTracks = (TextView)view.findViewById(R.id.media_stats_tracks);
mStatsPlaytime = (TextView)view.findViewById(R.id.media_stats_playtime);
+ mMediaDirectories = (TextView)view.findViewById(R.id.media_directories);
mFullScanCheck = (CheckBox)view.findViewById(R.id.media_scan_full);
mDropDbCheck = (CheckBox)view.findViewById(R.id.media_scan_drop_db);
mGroupAlbumsCheck = (CheckBox)view.findViewById(R.id.media_scan_group_albums);
@@ -109,6 +124,7 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
// Bind onClickListener to some elements
mStartButton.setOnClickListener(this);
mCancelButton.setOnClickListener(this);
+ mEditButton.setOnClickListener(this);
mGroupAlbumsCheck.setOnClickListener(this);
mForceBastpCheck.setOnClickListener(this);
}
@@ -129,6 +145,9 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
});
}}), 0, 200);
+ if (mIsEditingDirectories)
+ mIsEditingDirectories = false;
+
updatePreferences(null);
}
@@ -140,7 +159,7 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
mTimer = null;
}
- if (mFullScanPending) {
+ if (mFullScanPending && !mIsEditingDirectories) {
MediaLibrary.startLibraryScan(getActivity(), true, true);
mFullScanPending = false;
}
@@ -155,6 +174,11 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
case R.id.cancel_button:
cancelButtonPressed(view);
break;
+ case R.id.edit_button:
+ mIsEditingDirectories = true;
+ mFullScanPending = true;
+ startActivity(new Intent(getActivity(), MediaFoldersSelectionActivity.class));
+ break;
case R.id.media_scan_group_albums:
case R.id.media_scan_force_bastp:
confirmUpdatePreferences((CheckBox)view);
@@ -212,6 +236,16 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
mGroupAlbumsCheck.setChecked(prefs.groupAlbumsByFolder);
mForceBastpCheck.setChecked(prefs.forceBastp);
+
+ String txt = "";
+ for (String path : prefs.mediaFolders) {
+ txt += "✔ " + path + "\n";
+ }
+ for (String path : prefs.blacklistedFolders) {
+ txt += "✘ " + path + "\n";
+ }
+ mMediaDirectories.setText(txt);
+
}
/**
@@ -227,6 +261,7 @@ public class PreferencesMediaLibrary extends Fragment implements View.OnClickLis
mProgressBar.setProgress(progress.seen);
mStartButton.setEnabled(idle);
+ mEditButton.setEnabled(idle);
mDropDbCheck.setEnabled(idle);
mFullScanCheck.setEnabled(idle);
mForceBastpCheck.setEnabled(idle);