Audinaut-subsonic-app-android/app/src/main/java/net/nullsum/audinaut/fragments/SubsonicFragment.java

1528 lines
57 KiB
Java

/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package net.nullsum.audinaut.fragments;
import android.app.Activity;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.StatFs;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import net.nullsum.audinaut.R;
import net.nullsum.audinaut.activity.SubsonicActivity;
import net.nullsum.audinaut.adapter.SectionAdapter;
import net.nullsum.audinaut.domain.Artist;
import net.nullsum.audinaut.domain.Genre;
import net.nullsum.audinaut.domain.MusicDirectory;
import net.nullsum.audinaut.domain.Playlist;
import net.nullsum.audinaut.service.DownloadFile;
import net.nullsum.audinaut.service.DownloadService;
import net.nullsum.audinaut.service.MediaStoreService;
import net.nullsum.audinaut.service.MusicService;
import net.nullsum.audinaut.service.MusicServiceFactory;
import net.nullsum.audinaut.service.OfflineException;
import net.nullsum.audinaut.util.Constants;
import net.nullsum.audinaut.util.FileUtil;
import net.nullsum.audinaut.util.ImageLoader;
import net.nullsum.audinaut.util.LoadingTask;
import net.nullsum.audinaut.util.MenuUtil;
import net.nullsum.audinaut.util.ProgressListener;
import net.nullsum.audinaut.util.SilentBackgroundTask;
import net.nullsum.audinaut.util.SongDBHandler;
import net.nullsum.audinaut.util.UserUtil;
import net.nullsum.audinaut.util.Util;
import net.nullsum.audinaut.view.GridSpacingDecoration;
import net.nullsum.audinaut.view.PlaylistSongView;
import net.nullsum.audinaut.view.UpdateView;
import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import static net.nullsum.audinaut.domain.MusicDirectory.Entry;
public class SubsonicFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = SubsonicFragment.class.getSimpleName();
private static int TAG_INC = 10;
SubsonicActivity context;
CharSequence title = null;
CharSequence subtitle = null;
View rootView;
boolean primaryFragment = false;
boolean secondaryFragment = false;
boolean isOnlyVisible = true;
boolean alwaysFullscreen = false;
boolean alwaysStartFullscreen = false;
boolean invalidated = false;
GestureDetector gestureScanner;
boolean artist = false;
SwipeRefreshLayout refreshLayout;
MenuItem searchItem;
private boolean artistOverride = false;
private boolean firstRun;
private SearchView searchView;
private int tag;
public SubsonicFragment() {
super();
tag = TAG_INC++;
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
if (bundle != null) {
String name = bundle.getString(Constants.FRAGMENT_NAME);
if (name != null) {
title = name;
}
}
firstRun = true;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (title != null) {
outState.putString(Constants.FRAGMENT_NAME, title.toString());
}
}
@Override
public void onResume() {
super.onResume();
if (firstRun) {
firstRun = false;
} else {
UpdateView.triggerUpdate();
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
context = (SubsonicActivity) activity;
}
public void setContext(SubsonicActivity context) {
this.context = context;
}
void onFinishSetupOptionsMenu(final Menu menu) {
searchItem = menu.findItem(R.id.menu_global_search);
if (searchItem != null) {
searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
SearchManager searchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
SearchableInfo searchableInfo = searchManager.getSearchableInfo(context.getComponentName());
if (searchableInfo == null) {
Log.w(TAG, "Failed to get SearchableInfo");
} else {
searchView.setSearchableInfo(searchableInfo);
}
String currentQuery = getCurrentQuery();
if (currentQuery != null) {
searchView.setOnSearchClickListener(v -> searchView.setQuery(getCurrentQuery(), false));
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_global_shuffle:
onShuffleRequested();
return true;
case R.id.menu_refresh:
refresh();
return true;
case R.id.menu_play_now:
playNow(false, false);
return true;
case R.id.menu_play_last:
playNow(false, true);
return true;
case R.id.menu_play_next:
playNow(false, true, true);
return true;
case R.id.menu_shuffle:
playNow(true, false);
return true;
case R.id.menu_download:
downloadBackground(false);
clearSelected();
return true;
case R.id.menu_cache:
downloadBackground(true);
clearSelected();
return true;
case R.id.menu_delete:
delete();
clearSelected();
return true;
case R.id.menu_add_playlist:
List<Entry> songs = getSelectedEntries();
addToPlaylist(songs);
clearSelected();
return true;
}
return false;
}
void onCreateContextMenuSupport(Menu menu, MenuInflater menuInflater, UpdateView updateView, Object selected) {
if (selected instanceof Entry) {
Entry entry = (Entry) selected;
if (entry.isDirectory()) {
if (Util.isOffline(context)) {
menuInflater.inflate(R.menu.select_album_context_offline, menu);
} else {
menuInflater.inflate(R.menu.select_album_context, menu);
}
} else {
if (Util.isOffline(context)) {
menuInflater.inflate(R.menu.select_song_context_offline, menu);
} else {
menuInflater.inflate(R.menu.select_song_context, menu);
String songPressAction = Util.getSongPressAction(context);
if (!"next".equals(songPressAction) && !"last".equals(songPressAction)) {
menu.setGroupVisible(R.id.hide_play_now, false);
}
}
}
if (!isShowArtistEnabled() || (entry.getArtistId() == null)) {
menu.setGroupVisible(R.id.hide_show_artist, false);
}
} else if (selected instanceof Artist) {
if (Util.isOffline(context)) {
menuInflater.inflate(R.menu.select_artist_context_offline, menu);
} else {
menuInflater.inflate(R.menu.select_artist_context, menu);
}
}
MenuUtil.hideMenuItems(context, menu, updateView);
}
void recreateContextMenu(Menu menu) {
List<MenuItem> menuItems = new ArrayList<>();
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
if (item.isVisible()) {
menuItems.add(item);
}
}
menu.clear();
for (int i = 0; i < menuItems.size(); i++) {
MenuItem item = menuItems.get(i);
menu.add(tag, item.getItemId(), Menu.NONE, item.getTitle());
}
}
// For reverting specific removals: https://github.com/daneren2005/Subsonic/commit/fbd1a68042dfc3601eaa0a9e37b3957bbdd51420
boolean onContextItemSelected(MenuItem menuItem, Object selectedItem) {
Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null;
Entry entry = selectedItem instanceof Entry ? (Entry) selectedItem : null;
if (selectedItem instanceof DownloadFile) {
entry = ((DownloadFile) selectedItem).getSong();
}
List<Entry> songs = new ArrayList<>(1);
songs.add(entry);
switch (menuItem.getItemId()) {
case R.id.artist_menu_play_now:
downloadRecursively(artist.getId(), false, false, true, false, false, false);
break;
case R.id.artist_menu_play_shuffled:
downloadRecursively(artist.getId(), false, false, true, true, false, false);
break;
case R.id.artist_menu_play_next:
downloadRecursively(artist.getId(), false, true, false, false, false, true);
break;
case R.id.artist_menu_play_last:
downloadRecursively(artist.getId(), false, true, false, false, false, false);
break;
case R.id.artist_menu_download:
downloadRecursively(artist.getId(), false, true, false, false, true, false);
break;
case R.id.artist_menu_pin:
downloadRecursively(artist.getId(), true, true, false, false, true, false);
break;
case R.id.artist_menu_delete:
deleteRecursively(artist);
break;
case R.id.album_menu_play_now:
artistOverride = true;
downloadRecursively(entry.getId(), false, false, true, false, false, false);
break;
case R.id.album_menu_play_shuffled:
artistOverride = true;
downloadRecursively(entry.getId(), false, false, true, true, false, false);
break;
case R.id.album_menu_play_next:
artistOverride = true;
downloadRecursively(entry.getId(), false, true, false, false, false, true);
break;
case R.id.album_menu_play_last:
artistOverride = true;
downloadRecursively(entry.getId(), false, true, false, false, false, false);
break;
case R.id.album_menu_download:
artistOverride = true;
downloadRecursively(entry.getId(), false, true, false, false, true, false);
break;
case R.id.album_menu_pin:
artistOverride = true;
downloadRecursively(entry.getId(), true, true, false, false, true, false);
break;
case R.id.album_menu_delete:
deleteRecursively(entry);
break;
case R.id.album_menu_info:
displaySongInfo(entry);
break;
case R.id.album_menu_show_artist:
showAlbumArtist((Entry) selectedItem);
break;
case R.id.song_menu_play_now:
playNow(songs);
break;
case R.id.song_menu_play_next:
getDownloadService().download(songs, false, false, true, false);
break;
case R.id.song_menu_play_last:
getDownloadService().download(songs, false, false, false, false);
break;
case R.id.song_menu_download:
getDownloadService().downloadBackground(songs, false);
break;
case R.id.song_menu_pin:
getDownloadService().downloadBackground(songs, true);
break;
case R.id.song_menu_delete:
deleteSongs(songs);
break;
case R.id.song_menu_add_playlist:
addToPlaylist(songs);
break;
case R.id.song_menu_info:
displaySongInfo(entry);
break;
case R.id.song_menu_show_album:
showAlbum((Entry) selectedItem);
break;
case R.id.song_menu_show_artist:
showArtist((Entry) selectedItem);
break;
default:
return false;
}
return true;
}
void replaceFragment(SubsonicFragment fragment) {
context.replaceFragment(fragment, fragment.getSupportTag(), secondaryFragment);
}
void removeCurrent() {
context.removeCurrent();
}
public int getRootId() {
return rootView.getId();
}
public int getSupportTag() {
return tag;
}
public void setSupportTag(String tag) {
this.tag = Integer.parseInt(tag);
}
public void setPrimaryFragment(boolean primary) {
primaryFragment = primary;
if (primary) {
if (context != null && title != null) {
context.setTitle(title);
context.setSubtitle(subtitle);
}
if (invalidated) {
invalidated = false;
refresh(false);
}
}
}
public void setPrimaryFragment(boolean primary, boolean secondary) {
setPrimaryFragment(primary);
secondaryFragment = secondary;
}
public void setSecondaryFragment(boolean secondary) {
secondaryFragment = secondary;
}
public void setIsOnlyVisible(boolean isOnlyVisible) {
this.isOnlyVisible = isOnlyVisible;
}
public boolean isAlwaysFullscreen() {
return alwaysFullscreen;
}
public boolean isAlwaysStartFullscreen() {
return alwaysStartFullscreen;
}
public void invalidate() {
if (primaryFragment) {
refresh(true);
} else {
invalidated = true;
}
}
public DownloadService getDownloadService() {
return context != null ? context.getDownloadService() : null;
}
private void refresh() {
refresh(true);
}
void refresh(boolean refresh) {
}
@Override
public void onRefresh() {
refreshLayout.setRefreshing(false);
refresh();
}
public void setProgressVisible(boolean visible) {
View view = rootView.findViewById(R.id.tab_progress);
if (view != null) {
view.setVisibility(visible ? View.VISIBLE : View.GONE);
if (visible) {
View progress = rootView.findViewById(R.id.tab_progress_spinner);
progress.setVisibility(View.VISIBLE);
}
}
}
public void updateProgress(String message) {
TextView view = rootView.findViewById(R.id.tab_progress_message);
if (view != null) {
view.setText(message);
}
}
void setEmpty(boolean empty) {
View view = rootView.findViewById(R.id.tab_progress);
if (empty) {
view.setVisibility(View.VISIBLE);
View progress = view.findViewById(R.id.tab_progress_spinner);
progress.setVisibility(View.GONE);
TextView text = view.findViewById(R.id.tab_progress_message);
text.setText(R.string.common_empty);
} else {
view.setVisibility(View.GONE);
}
}
synchronized ImageLoader getImageLoader() {
return context.getImageLoader();
}
void setTitle(CharSequence title) {
this.title = title;
context.setTitle(title);
}
void setSubtitle(CharSequence title) {
this.subtitle = title;
context.setSubtitle(title);
}
public CharSequence getTitle() {
return this.title;
}
void setTitle(int title) {
this.title = context.getResources().getString(title);
context.setTitle(this.title);
}
void setupScrollList(final RecyclerView recyclerView) {
if (!context.isTouchscreen()) {
refreshLayout.setEnabled(false);
} else {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
refreshLayout.setEnabled(!recyclerView.canScrollVertically(-1));
}
});
}
}
void setupLayoutManager(RecyclerView recyclerView, boolean largeAlbums) {
recyclerView.setLayoutManager(getLayoutManager(recyclerView, largeAlbums));
}
private RecyclerView.LayoutManager getLayoutManager(RecyclerView recyclerView, boolean largeCells) {
if (largeCells) {
return getGridLayoutManager(recyclerView);
} else {
return getLinearLayoutManager();
}
}
private GridLayoutManager getGridLayoutManager(RecyclerView recyclerView) {
final int columns = getRecyclerColumnCount();
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, columns);
GridLayoutManager.SpanSizeLookup spanSizeLookup = getSpanSizeLookup(gridLayoutManager);
if (spanSizeLookup != null) {
gridLayoutManager.setSpanSizeLookup(spanSizeLookup);
}
RecyclerView.ItemDecoration itemDecoration = getItemDecoration();
if (itemDecoration != null) {
recyclerView.addItemDecoration(itemDecoration);
}
return gridLayoutManager;
}
private LinearLayoutManager getLinearLayoutManager() {
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
return layoutManager;
}
GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
return new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
SectionAdapter adapter = getCurrentAdapter();
if (adapter != null) {
int viewType = adapter.getItemViewType(position);
if (viewType == SectionAdapter.VIEW_TYPE_HEADER) {
return gridLayoutManager.getSpanCount();
} else {
return 1;
}
} else {
return 1;
}
}
};
}
private RecyclerView.ItemDecoration getItemDecoration() {
return new GridSpacingDecoration();
}
int getRecyclerColumnCount() {
if (isOnlyVisible) {
return context.getResources().getInteger(R.integer.Grid_FullScreen_Columns);
} else {
return context.getResources().getInteger(R.integer.Grid_Columns);
}
}
void warnIfStorageUnavailable() {
if (!Util.isExternalStoragePresent()) {
Util.toast(context, R.string.select_album_no_sdcard);
}
try {
StatFs stat = new StatFs(FileUtil.getMusicDirectory(context).getPath());
long bytesAvailableFs = stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
if (bytesAvailableFs < 50000000L) {
Util.toast(context, context.getResources().getString(R.string.select_album_no_room, Util.formatBytes(bytesAvailableFs)));
}
} catch (Exception e) {
Log.w(TAG, "Error while checking storage space for music directory", e);
}
}
private void onShuffleRequested() {
if (Util.isOffline(context)) {
DownloadService downloadService = getDownloadService();
if (downloadService == null) {
return;
}
downloadService.clear();
downloadService.setShufflePlayEnabled(true);
context.openNowPlaying();
return;
}
View dialogView = context.getLayoutInflater().inflate(R.layout.shuffle_dialog, null);
final EditText startYearBox = dialogView.findViewById(R.id.start_year);
final EditText endYearBox = dialogView.findViewById(R.id.end_year);
final EditText genreBox = dialogView.findViewById(R.id.genre);
final Button genreCombo = dialogView.findViewById(R.id.genre_combo);
final SharedPreferences prefs = Util.getPreferences(context);
final String oldStartYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "");
final String oldEndYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "");
final String oldGenre = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "");
boolean _useCombo;
genreBox.setVisibility(View.GONE);
genreCombo.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new LoadingTask<List<Genre>>(context, true) {
@Override
protected List<Genre> doInBackground() throws Throwable {
MusicService musicService = MusicServiceFactory.getMusicService(context);
return musicService.getGenres(false, context, this);
}
@Override
protected void done(final List<Genre> genres) {
List<String> names = new ArrayList<>();
String blank = context.getResources().getString(R.string.select_genre_blank);
names.add(blank);
for (Genre genre : genres) {
names.add(genre.getName());
}
final List<String> finalNames = names;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.shuffle_pick_genre)
.setItems(names.toArray(new CharSequence[names.size()]), (dialog, which) -> {
if (which == 0) {
genreCombo.setText("");
} else {
genreCombo.setText(finalNames.get(which));
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
protected void error(Throwable error) {
String msg;
if (error instanceof OfflineException) {
msg = getErrorMessage(error);
} else {
msg = context.getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error);
}
Util.toast(context, msg, false);
}
}.execute();
}
});
_useCombo = true;
final boolean useCombo = _useCombo;
startYearBox.setText(oldStartYear);
endYearBox.setText(oldEndYear);
genreBox.setText(oldGenre);
genreCombo.setText(oldGenre);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.shuffle_title)
.setView(dialogView)
.setPositiveButton(R.string.common_ok, (dialog, id) -> {
String genre;
if (useCombo) {
genre = genreCombo.getText().toString();
} else {
genre = genreBox.getText().toString();
}
String startYear = startYearBox.getText().toString();
String endYear = endYearBox.getText().toString();
SharedPreferences.Editor editor = prefs.edit();
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, startYear);
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, endYear);
editor.putString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, genre);
editor.apply();
DownloadService downloadService = getDownloadService();
if (downloadService == null) {
return;
}
downloadService.clear();
downloadService.setShufflePlayEnabled(true);
context.openNowPlaying();
})
.setNegativeButton(R.string.common_cancel, null);
AlertDialog dialog = builder.create();
dialog.show();
}
void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background, final boolean playNext) {
downloadRecursively(id, "", true, save, append, autoplay, shuffle, background, playNext);
}
void downloadPlaylist(final String id, final String name) {
downloadRecursively(id, name);
}
private void downloadRecursively(final String id, final String name) {
downloadRecursively(id, name, false, false, true, false, false, true, false);
}
private void downloadRecursively(final String id, final String name, final boolean isDirectory, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background, final boolean playNext) {
new RecursiveLoader(context) {
@Override
protected Boolean doInBackground() throws Throwable {
musicService = MusicServiceFactory.getMusicService(context);
MusicDirectory root;
if (isDirectory && id != null) {
root = getMusicDirectory(id, name, false, musicService, this);
} else {
root = musicService.getPlaylist(true, id, name, context, this);
}
boolean shuffleByAlbum = Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_SHUFFLE_BY_ALBUM, true);
if (shuffle && shuffleByAlbum) {
Collections.shuffle(root.getChildren());
}
songs = new LinkedList<>();
getSongsRecursively(root, songs);
if (shuffle && !shuffleByAlbum) {
Collections.shuffle(songs);
}
DownloadService downloadService = getDownloadService();
boolean transition = false;
if (!songs.isEmpty() && downloadService != null) {
// Conditions for a standard play now operation
if (!append && !save && autoplay && !playNext && !shuffle && !background) {
playNowOverride = true;
return false;
}
if (!append && !background) {
downloadService.clear();
}
if (!background) {
downloadService.download(songs, save, autoplay, playNext, false);
if (!append) {
transition = true;
}
} else {
downloadService.downloadBackground(songs, save);
}
}
artistOverride = false;
return transition;
}
}.execute();
}
void downloadRecursively(final List<Entry> albums, final boolean shuffle, final boolean append, final boolean playNext) {
new RecursiveLoader(context) {
@Override
protected Boolean doInBackground() throws Throwable {
musicService = MusicServiceFactory.getMusicService(context);
if (shuffle) {
Collections.shuffle(albums);
}
songs = new LinkedList<>();
MusicDirectory root = new MusicDirectory();
root.addChildren(albums);
getSongsRecursively(root, songs);
DownloadService downloadService = getDownloadService();
boolean transition = false;
if (!songs.isEmpty() && downloadService != null) {
// Conditions for a standard play now operation
if (!append && !shuffle) {
playNowOverride = true;
return false;
}
if (!append) {
downloadService.clear();
}
downloadService.download(songs, false, true, playNext, false);
if (!append) {
transition = true;
}
}
artistOverride = false;
return transition;
}
}.execute();
}
MusicDirectory getMusicDirectory(String id, String name, boolean refresh, MusicService service, ProgressListener listener) throws Exception {
return getMusicDirectory(id, name, refresh, false, service, listener);
}
MusicDirectory getMusicDirectory(String id, String name, boolean refresh, boolean forceArtist, MusicService service, ProgressListener listener) throws Exception {
if (!Util.isOffline(context)) {
if (artist && !artistOverride || forceArtist) {
return service.getArtist(id, name, refresh, context, listener);
} else {
return service.getAlbum(id, name, refresh, context, listener);
}
} else {
return service.getMusicDirectory(id, name, refresh, context, listener);
}
}
private void addToPlaylist(final List<Entry> songs) {
Iterator<Entry> it = songs.iterator();
while (it.hasNext()) {
Entry entry = it.next();
if (entry.isDirectory()) {
it.remove();
}
}
if (songs.isEmpty()) {
Util.toast(context, "No songs selected");
return;
}
new LoadingTask<List<Playlist>>(context, true) {
@Override
protected List<Playlist> doInBackground() throws Throwable {
MusicService musicService = MusicServiceFactory.getMusicService(context);
List<Playlist> playlists = new ArrayList<>();
playlists.addAll(musicService.getPlaylists(false, context, this));
// Iterate through and remove all non owned public playlists
Iterator<Playlist> it = playlists.iterator();
while (it.hasNext()) {
Playlist playlist = it.next();
if (playlist.getPublic() && !playlist.getId().contains(".m3u") && !UserUtil.getCurrentUsername(context).equals(playlist.getOwner())) {
it.remove();
}
}
return playlists;
}
@Override
protected void done(final List<Playlist> playlists) {
// Create adapter to show playlists
Playlist createNew = new Playlist("-1", context.getResources().getString(R.string.playlist_create_new));
playlists.add(0, createNew);
ArrayAdapter playlistAdapter = new ArrayAdapter<Playlist>(context, R.layout.basic_count_item, playlists) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Playlist playlist = getItem(position);
// Create new if not getting a convert view to use
PlaylistSongView view;
if (convertView instanceof PlaylistSongView) {
view = (PlaylistSongView) convertView;
} else {
view = new PlaylistSongView(context);
}
view.setObject(playlist, songs);
return view;
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.playlist_add_to)
.setAdapter(playlistAdapter, (dialog, which) -> {
if (which > 0) {
addToPlaylist(playlists.get(which), songs);
} else {
createNewPlaylist(songs, false);
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
protected void error(Throwable error) {
String msg;
if (error instanceof OfflineException) {
msg = getErrorMessage(error);
} else {
msg = context.getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error);
}
Util.toast(context, msg, false);
}
}.execute();
}
private void addToPlaylist(final Playlist playlist, final List<Entry> songs) {
new SilentBackgroundTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
MusicService musicService = MusicServiceFactory.getMusicService(context);
musicService.addToPlaylist(playlist.getId(), songs, context, null);
return null;
}
@Override
protected void done(Void result) {
Util.toast(context, context.getResources().getString(R.string.updated_playlist, songs.size(), playlist.getName()));
}
@Override
protected void error(Throwable error) {
String msg;
if (error instanceof OfflineException) {
msg = getErrorMessage(error);
} else {
msg = context.getResources().getString(R.string.updated_playlist_error, playlist.getName()) + " " + getErrorMessage(error);
}
Util.toast(context, msg, false);
}
}.execute();
}
void createNewPlaylist(final List<Entry> songs, final boolean getSuggestion) {
View layout = context.getLayoutInflater().inflate(R.layout.save_playlist, null);
final EditText playlistNameView = layout.findViewById(R.id.save_playlist_name);
final CheckBox overwriteCheckBox = layout.findViewById(R.id.save_playlist_overwrite);
if (getSuggestion) {
String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null;
if (playlistName != null) {
playlistNameView.setText(playlistName);
try {
if (Integer.parseInt(getDownloadService().getSuggestedPlaylistId()) != -1) {
overwriteCheckBox.setChecked(true);
overwriteCheckBox.setVisibility(View.VISIBLE);
}
} catch (Exception e) {
Log.i(TAG, "Playlist id isn't a integer, probably MusicCabinet");
}
} else {
DateFormat dateFormat = DateFormat.getDateInstance();
playlistNameView.setText(dateFormat.format(new Date()));
}
} else {
DateFormat dateFormat = DateFormat.getDateInstance();
playlistNameView.setText(dateFormat.format(new Date()));
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.download_playlist_title)
.setMessage(R.string.download_playlist_name)
.setView(layout)
.setPositiveButton(R.string.common_save, (dialog, id) -> {
String playlistName = String.valueOf(playlistNameView.getText());
if (overwriteCheckBox.isChecked()) {
overwritePlaylist(songs, playlistName, getDownloadService().getSuggestedPlaylistId());
} else {
createNewPlaylist(songs, playlistName);
if (getSuggestion) {
DownloadService downloadService = getDownloadService();
if (downloadService != null) {
downloadService.setSuggestedPlaylistName(playlistName, null);
}
}
}
})
.setNegativeButton(R.string.common_cancel, (dialog, id) -> dialog.cancel())
.setCancelable(true);
AlertDialog dialog = builder.create();
dialog.show();
}
private void createNewPlaylist(final List<Entry> songs, final String name) {
new SilentBackgroundTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
MusicService musicService = MusicServiceFactory.getMusicService(context);
musicService.createPlaylist(null, name, songs, context, null);
return null;
}
@Override
protected void done(Void result) {
Util.toast(context, R.string.download_playlist_done);
}
@Override
protected void error(Throwable error) {
String msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
Util.toast(context, msg);
}
}.execute();
}
private void overwritePlaylist(final List<Entry> songs, final String name, final String id) {
new SilentBackgroundTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
MusicService musicService = MusicServiceFactory.getMusicService(context);
MusicDirectory playlist = musicService.getPlaylist(true, id, name, context, null);
List<Entry> toDelete = playlist.getChildren();
musicService.overwritePlaylist(id, name, toDelete.size(), songs, context, null);
return null;
}
@Override
protected void done(Void result) {
Util.toast(context, R.string.download_playlist_done);
}
@Override
protected void error(Throwable error) {
String msg;
if (error instanceof OfflineException) {
msg = getErrorMessage(error);
} else {
msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error);
}
Util.toast(context, msg, false);
}
}.execute();
}
void displaySongInfo(final Entry song) {
Integer duration = null;
Integer bitrate = null;
String format = null;
long size = 0;
if (!song.isDirectory()) {
try {
DownloadFile downloadFile = new DownloadFile(context, song, false);
File file = downloadFile.getCompleteFile();
if (file.exists()) {
MediaMetadataRetriever metadata = new MediaMetadataRetriever();
metadata.setDataSource(file.getAbsolutePath());
String tmp = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
duration = Integer.parseInt((tmp != null) ? tmp : "0") / 1000;
format = FileUtil.getExtension(file.getName());
size = file.length();
// If no duration try to read bitrate tag
if (duration == null) {
tmp = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE);
bitrate = Integer.parseInt((tmp != null) ? tmp : "0") / 1000;
} else {
// Otherwise do a calculation for it
// Divide by 1000 so in kbps
bitrate = (int) (size / duration) / 1000 * 8;
}
if (Util.isOffline(context)) {
song.setGenre(metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE));
String year = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR);
song.setYear(Integer.parseInt((year != null) ? year : "0"));
}
}
} catch (Exception e) {
Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver");
}
}
if (duration == null) {
duration = song.getDuration();
}
List<Integer> headers = new ArrayList<>();
List<String> details = new ArrayList<>();
if (!song.isDirectory()) {
headers.add(R.string.details_title);
details.add(song.getTitle());
}
if (song.getArtist() != null && !"".equals(song.getArtist())) {
headers.add(R.string.details_artist);
details.add(song.getArtist());
}
if (song.getAlbum() != null && !"".equals(song.getAlbum())) {
headers.add(R.string.details_album);
details.add(song.getAlbum());
}
if (song.getTrack() != null && song.getTrack() != 0) {
headers.add(R.string.details_track);
details.add(Integer.toString(song.getTrack()));
}
if (song.getGenre() != null && !"".equals(song.getGenre())) {
headers.add(R.string.details_genre);
details.add(song.getGenre());
}
if (song.getYear() != null && song.getYear() != 0) {
headers.add(R.string.details_year);
details.add(Integer.toString(song.getYear()));
}
if (!Util.isOffline(context) && song.getSuffix() != null) {
headers.add(R.string.details_server_format);
details.add(song.getSuffix());
if (song.getBitRate() != null && song.getBitRate() != 0) {
headers.add(R.string.details_server_bitrate);
details.add(song.getBitRate() + " kbps");
}
}
if (format != null && !"".equals(format)) {
headers.add(R.string.details_cached_format);
details.add(format);
}
if (bitrate != null && bitrate != 0) {
headers.add(R.string.details_cached_bitrate);
details.add(bitrate + " kbps");
}
if (size != 0) {
headers.add(R.string.details_size);
details.add(Util.formatLocalizedBytes(size, context));
}
if (duration != null && duration != 0) {
headers.add(R.string.details_length);
details.add(Util.formatDuration(duration));
}
try {
Long[] dates = SongDBHandler.getHandler(context).getLastPlayed(song);
if (dates != null && dates[0] != null && dates[0] > 0) {
headers.add(R.string.details_last_played);
Date date = new Date((dates[1] != null && dates[1] > dates[0]) ? dates[1] : dates[0]);
DateFormat dateFormat = DateFormat.getDateInstance();
details.add(dateFormat.format(date));
}
} catch (Exception e) {
Log.e(TAG, "Failed to get last played", e);
}
int title;
if (song.isDirectory()) {
title = R.string.details_title_album;
} else {
title = R.string.details_title_song;
}
Util.showDetailsDialog(context, title, headers, details);
}
private void deleteRecursively(Artist artist) {
deleteRecursively(artist, FileUtil.getArtistDirectory(context, artist));
}
void deleteRecursively(Entry album) {
deleteRecursively(album, FileUtil.getAlbumDirectory(context, album));
}
private void deleteRecursively(final Object remove, final File dir) {
if (dir == null) {
return;
}
new LoadingTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
MediaStoreService mediaStore = new MediaStoreService(context);
FileUtil.recursiveDelete(dir, mediaStore);
return null;
}
@Override
protected void done(Void result) {
if (Util.isOffline(context)) {
SectionAdapter adapter = getCurrentAdapter();
if (adapter != null) {
adapter.removeItem(remove);
} else {
refresh();
}
} else {
UpdateView.triggerUpdate();
}
}
}.execute();
}
private void deleteSongs(final List<Entry> songs) {
new LoadingTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
getDownloadService().delete(songs);
return null;
}
@Override
protected void done(Void result) {
if (Util.isOffline(context)) {
SectionAdapter adapter = getCurrentAdapter();
if (adapter != null) {
for (Entry song : songs) {
adapter.removeItem(song);
}
} else {
refresh();
}
} else {
UpdateView.triggerUpdate();
}
}
}.execute();
}
private void showAlbumArtist(Entry entry) {
SubsonicFragment fragment = new SelectDirectoryFragment();
Bundle args = new Bundle();
args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getArtistId());
args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getArtist());
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
fragment.setArguments(args);
replaceFragment(fragment);
}
private void showArtist(Entry entry) {
SubsonicFragment fragment = new SelectDirectoryFragment();
Bundle args = new Bundle();
args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getArtistId());
args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getArtist());
args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
fragment.setArguments(args);
replaceFragment(fragment);
}
private void showAlbum(Entry entry) {
SubsonicFragment fragment = new SelectDirectoryFragment();
Bundle args = new Bundle();
args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getAlbumId());
args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getAlbum());
fragment.setArguments(args);
replaceFragment(fragment);
}
public GestureDetector getGestureDetector() {
return gestureScanner;
}
void onSongPress(List<Entry> entries, Entry entry) {
onSongPress(entries, entry, true);
}
void onSongPress(List<Entry> entries, Entry entry, boolean allowPlayAll) {
List<Entry> songs = new ArrayList<>();
String songPressAction = Util.getSongPressAction(context);
if ("all".equals(songPressAction) && allowPlayAll) {
for (Entry song : entries) {
if (!song.isDirectory()) {
songs.add(song);
}
}
playNow(songs, entry);
} else if ("next".equals(songPressAction)) {
getDownloadService().download(Collections.singletonList(entry), false, false, true, false);
} else if ("last".equals(songPressAction)) {
getDownloadService().download(Collections.singletonList(entry), false, false, false, false);
} else {
songs.add(entry);
playNow(songs);
}
}
private void playNow(List<Entry> entries) {
playNow(entries, null, null);
}
private void playNow(final List<Entry> entries, final String playlistName, final String playlistId) {
new RecursiveLoader(context) {
@Override
protected Boolean doInBackground() throws Throwable {
getSongsRecursively(entries, songs);
return null;
}
@Override
protected void done(Boolean result) {
Entry selected = songs.isEmpty() ? null : songs.get(0);
playNow(songs, selected, playlistName, playlistId);
}
}.execute();
}
private void playNow(List<Entry> entries, Entry song) {
playNow(entries, song, null, null);
}
private void playNow(final List<Entry> entries, final Entry song, final String playlistName, final String playlistId) {
new LoadingTask<Void>(context) {
@Override
protected Void doInBackground() throws Throwable {
playNowInTask(entries, song, playlistName, playlistId);
return null;
}
@Override
protected void done(Void result) {
context.openNowPlaying();
}
}.execute();
}
private void playNowInTask(final List<Entry> entries, final Entry song, final String playlistName, final String playlistId) {
DownloadService downloadService = getDownloadService();
if (downloadService == null) {
return;
}
downloadService.clear();
downloadService.download(entries, false, true, true, false, entries.indexOf(song));
downloadService.setSuggestedPlaylistName(playlistName, playlistId);
}
public SectionAdapter getCurrentAdapter() {
return null;
}
public void stopActionMode() {
SectionAdapter adapter = getCurrentAdapter();
if (adapter != null) {
adapter.stopActionMode();
}
}
private void clearSelected() {
if (getCurrentAdapter() != null) {
getCurrentAdapter().clearSelected();
}
}
List<Entry> getSelectedEntries() {
return getCurrentAdapter().getSelected();
}
private void playNow(final boolean shuffle, final boolean append) {
playNow(shuffle, append, false);
}
void playNow(final boolean shuffle, final boolean append, final boolean playNext) {
List<Entry> songs = getSelectedEntries();
if (!songs.isEmpty()) {
download(songs, append, !append, playNext, shuffle);
clearSelected();
}
}
void download(List<Entry> entries, boolean append, boolean autoplay, boolean playNext, boolean shuffle) {
download(entries, append, autoplay, playNext, shuffle, null, null);
}
void download(final List<Entry> entries, final boolean append, final boolean autoplay, final boolean playNext, final boolean shuffle, final String playlistName, final String playlistId) {
final DownloadService downloadService = getDownloadService();
if (downloadService == null) {
return;
}
warnIfStorageUnavailable();
// Conditions for using play now button
if (!append && autoplay && !playNext && !shuffle) {
// Call playNow which goes through and tries to use information
playNow(entries, playlistName, playlistId);
return;
}
RecursiveLoader onValid = new RecursiveLoader(context) {
@Override
protected Boolean doInBackground() throws Throwable {
if (!append) {
getDownloadService().clear();
}
getSongsRecursively(entries, songs);
downloadService.download(songs, false, autoplay, playNext, shuffle);
if (playlistName != null) {
downloadService.setSuggestedPlaylistName(playlistName, playlistId);
} else {
downloadService.setSuggestedPlaylistName(null, null);
}
return null;
}
@Override
protected void done(Boolean result) {
if (autoplay) {
context.openNowPlaying();
} else if (append) {
Util.toast(context,
context.getResources().getQuantityString(R.plurals.select_album_n_songs_added, songs.size(), songs.size()));
}
}
};
executeOnValid(onValid);
}
private void executeOnValid(RecursiveLoader onValid) {
onValid.execute();
}
void downloadBackground(final boolean save) {
List<Entry> songs = getSelectedEntries();
if (!songs.isEmpty()) {
downloadBackground(save, songs);
}
}
void downloadBackground(final boolean save, final List<Entry> entries) {
if (getDownloadService() == null) {
return;
}
warnIfStorageUnavailable();
new RecursiveLoader(context) {
@Override
protected Boolean doInBackground() throws Throwable {
getSongsRecursively(entries);
getDownloadService().downloadBackground(songs, save);
return null;
}
@Override
protected void done(Boolean result) {
Util.toast(context, context.getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size()));
}
}.execute();
}
void delete() {
List<Entry> songs = getSelectedEntries();
if (!songs.isEmpty()) {
DownloadService downloadService = getDownloadService();
if (downloadService != null) {
downloadService.delete(songs);
}
}
}
boolean isShowArtistEnabled() {
return false;
}
String getCurrentQuery() {
return null;
}
public abstract class RecursiveLoader extends LoadingTask<Boolean> {
static final int MAX_SONGS = 500;
MusicService musicService;
boolean playNowOverride = false;
List<Entry> songs = new ArrayList<>();
public RecursiveLoader(Activity context) {
super(context);
musicService = MusicServiceFactory.getMusicService(context);
}
void getSongsRecursively(List<Entry> entry) throws Exception {
getSongsRecursively(entry, songs);
}
void getSongsRecursively(List<Entry> entry, List<Entry> songs) throws Exception {
MusicDirectory dir = new MusicDirectory();
dir.addChildren(entry);
getSongsRecursively(dir, songs);
}
void getSongsRecursively(MusicDirectory parent, List<Entry> songs) throws Exception {
if (songs.size() > MAX_SONGS) {
return;
}
for (Entry dir : parent.getChildren(true, false)) {
MusicDirectory musicDirectory;
if (!Util.isOffline(context)) {
musicDirectory = musicService.getAlbum(dir.getId(), dir.getTitle(), false, context, this);
} else {
musicDirectory = musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, context, this);
}
getSongsRecursively(musicDirectory, songs);
}
songs.addAll(parent.getChildren(false, true));
}
@Override
protected void done(Boolean result) {
warnIfStorageUnavailable();
if (playNowOverride) {
playNow(songs);
return;
}
if (result) {
context.openNowPlaying();
}
}
}
}