Java tutorial
/* 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 github.daneren2005.dsub.fragments; import android.annotation.TargetApi; import android.app.Activity; import android.support.v7.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.StatFs; import android.support.v4.app.Fragment; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; 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.AbsListView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.RatingBar; import android.widget.TextView; import github.daneren2005.dsub.R; import github.daneren2005.dsub.activity.SubsonicActivity; import github.daneren2005.dsub.activity.SubsonicFragmentActivity; import github.daneren2005.dsub.adapter.SectionAdapter; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Bookmark; import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.ServerInfo; import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.MediaStoreService; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.service.OfflineException; import github.daneren2005.dsub.service.ServerTooOldException; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.ImageLoader; import github.daneren2005.dsub.util.MenuUtil; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.SilentBackgroundTask; import github.daneren2005.dsub.util.LoadingTask; import github.daneren2005.dsub.util.UserUtil; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.AlbumView; import github.daneren2005.dsub.view.ArtistEntryView; import github.daneren2005.dsub.view.ArtistView; import github.daneren2005.dsub.view.GridSpacingDecoration; import github.daneren2005.dsub.view.PlaylistSongView; import github.daneren2005.dsub.view.SongView; import github.daneren2005.dsub.view.UpdateView; import java.io.File; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Random; import static github.daneren2005.dsub.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; private int tag; protected SubsonicActivity context; protected CharSequence title = null; protected CharSequence subtitle = null; protected View rootView; protected boolean primaryFragment = false; protected boolean secondaryFragment = false; protected boolean invalidated = false; protected static Random random = new Random(); protected GestureDetector gestureScanner; protected Share share; protected boolean artist = false; protected boolean artistOverride = false; protected SwipeRefreshLayout refreshLayout; protected boolean firstRun; 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 onDestroy() { super.onDestroy(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); context = (SubsonicActivity) activity; } public void setContext(SubsonicActivity context) { this.context = context; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_global_shuffle: onShuffleRequested(); return true; case R.id.menu_global_search: context.onSearchRequested(); return true; case R.id.menu_exit: exit(); 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; } public void onCreateContextMenuSupport(Menu menu, MenuInflater menuInflater, UpdateView updateView, Object selected) { if (selected instanceof Entry) { Entry entry = (Entry) selected; if (entry instanceof PodcastEpisode && !entry.isVideo()) { if (Util.isOffline(context)) { menuInflater.inflate(R.menu.select_podcast_episode_context_offline, menu); } else { menuInflater.inflate(R.menu.select_podcast_episode_context, menu); if (entry.getBookmark() == null) { menu.removeItem(R.id.bookmark_menu_delete); } } } else 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); if (Util.isTagBrowsing(context)) { menu.removeItem(R.id.menu_rate); } } menu.findItem(entry.isDirectory() ? R.id.album_menu_star : R.id.song_menu_star) .setTitle(entry.isStarred() ? R.string.common_unstar : R.string.common_star); } else if (!entry.isVideo()) { if (Util.isOffline(context)) { menuInflater.inflate(R.menu.select_song_context_offline, menu); } else { menuInflater.inflate(R.menu.select_song_context, menu); if (entry.getBookmark() == null) { menu.removeItem(R.id.bookmark_menu_delete); } } menu.findItem(entry.isDirectory() ? R.id.album_menu_star : R.id.song_menu_star) .setTitle(entry.isStarred() ? R.string.common_unstar : R.string.common_star); } else { if (Util.isOffline(context)) { menuInflater.inflate(R.menu.select_video_context_offline, menu); } else { menuInflater.inflate(R.menu.select_video_context, menu); } } } else if (selected instanceof Artist) { Artist artist = (Artist) selected; if (Util.isOffline(context)) { menuInflater.inflate(R.menu.select_artist_context_offline, menu); } else { menuInflater.inflate(R.menu.select_artist_context, menu); menu.findItem(R.id.artist_menu_star) .setTitle(artist.isStarred() ? R.string.common_unstar : R.string.common_star); } } MenuUtil.hideMenuItems(context, menu, updateView); } protected void recreateContextMenu(Menu menu) { List<MenuItem> menuItems = new ArrayList<MenuItem>(); 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 public boolean onContextItemSelected(MenuItem menuItem, Object selectedItem) { Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null; Entry entry = selectedItem instanceof Entry ? (Entry) selectedItem : null; List<Entry> songs = new ArrayList<Entry>(1); songs.add(entry); switch (menuItem.getItemId()) { case R.id.artist_menu_play_now: downloadRecursively(artist.getId(), false, false, true, false, false); break; case R.id.artist_menu_play_shuffled: downloadRecursively(artist.getId(), false, false, true, true, 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); break; case R.id.artist_menu_download: downloadRecursively(artist.getId(), false, true, false, false, true); break; case R.id.artist_menu_pin: downloadRecursively(artist.getId(), true, true, false, false, true); break; case R.id.artist_menu_delete: deleteRecursively(artist); break; case R.id.artist_menu_star: toggleStarred(artist); break; case R.id.album_menu_play_now: artistOverride = true; downloadRecursively(entry.getId(), false, false, true, false, false); break; case R.id.album_menu_play_shuffled: artistOverride = true; downloadRecursively(entry.getId(), false, false, true, true, 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); break; case R.id.album_menu_download: artistOverride = true; downloadRecursively(entry.getId(), false, true, false, false, true); break; case R.id.album_menu_pin: artistOverride = true; downloadRecursively(entry.getId(), true, true, false, false, true); break; case R.id.album_menu_star: toggleStarred(entry); 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.album_menu_share: createShare(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_star: toggleStarred(entry); break; case R.id.song_menu_play_external: playExternalPlayer(entry); break; case R.id.song_menu_info: displaySongInfo(entry); break; case R.id.song_menu_stream_external: streamExternalPlayer(entry); break; case R.id.song_menu_share: createShare(songs); break; case R.id.song_menu_show_album: showAlbum((Entry) selectedItem); break; case R.id.song_menu_show_artist: showArtist((Entry) selectedItem); break; case R.id.bookmark_menu_delete: deleteBookmark(entry, null); break; case R.id.menu_rate: setRating(entry); break; default: return false; } return true; } public void replaceFragment(SubsonicFragment fragment) { replaceFragment(fragment, true); } public void replaceFragment(SubsonicFragment fragment, boolean replaceCurrent) { context.replaceFragment(fragment, fragment.getSupportTag(), secondaryFragment && replaceCurrent); } public void replaceExistingFragment(SubsonicFragment fragment) { context.replaceExistingFragment(fragment, fragment.getSupportTag()); } public int getRootId() { return rootView.getId(); } public void setSupportTag(int tag) { this.tag = tag; } public void setSupportTag(String tag) { this.tag = Integer.parseInt(tag); } public int getSupportTag() { return 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 invalidate() { if (primaryFragment) { refresh(true); } else { invalidated = true; } } public DownloadService getDownloadService() { return context != null ? context.getDownloadService() : null; } protected void refresh() { refresh(true); } protected void refresh(boolean refresh) { } @Override public void onRefresh() { refreshLayout.setRefreshing(false); refresh(); } protected void exit() { if (((Object) context).getClass() != SubsonicFragmentActivity.class) { Intent intent = new Intent(context, SubsonicFragmentActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true); Util.startActivityWithoutTransition(context, intent); } else { context.stopService(new Intent(context, DownloadService.class)); context.finish(); } } 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 = (TextView) rootView.findViewById(R.id.tab_progress_message); if (view != null) { view.setText(message); } } public 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 = (TextView) view.findViewById(R.id.tab_progress_message); text.setText(R.string.common_empty); } else { view.setVisibility(View.GONE); } } protected synchronized ImageLoader getImageLoader() { return context.getImageLoader(); } public synchronized static ImageLoader getStaticImageLoader(Context context) { return SubsonicActivity.getStaticImageLoader(context); } public void setTitle(CharSequence title) { this.title = title; context.setTitle(title); } public void setTitle(int title) { this.title = context.getResources().getString(title); context.setTitle(this.title); } public void setSubtitle(CharSequence title) { this.subtitle = title; context.setSubtitle(title); } public CharSequence getTitle() { return this.title; } protected void setupScrollList(final AbsListView listView) { if (!context.isTouchscreen()) { refreshLayout.setEnabled(false); } else { listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int topRowVerticalPosition = (listView.getChildCount() == 0) ? 0 : listView.getChildAt(0).getTop(); refreshLayout .setEnabled(topRowVerticalPosition >= 0 && listView.getFirstVisiblePosition() == 0); } }); refreshLayout.setColorScheme(R.color.holo_blue_light, R.color.holo_orange_light, R.color.holo_green_light, R.color.holo_red_light); } } protected void setupScrollList(final RecyclerView recyclerView) { if (!context.isTouchscreen()) { refreshLayout.setEnabled(false); } else { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { refreshLayout.setEnabled(!recyclerView.canScrollVertically(-1)); } }); refreshLayout.setColorScheme(R.color.holo_blue_light, R.color.holo_orange_light, R.color.holo_green_light, R.color.holo_red_light); } } public void setupLayoutManager(RecyclerView recyclerView, boolean largeAlbums) { recyclerView.setLayoutManager(getLayoutManager(recyclerView, largeAlbums)); } public RecyclerView.LayoutManager getLayoutManager(RecyclerView recyclerView, boolean largeCells) { if (largeCells) { return getGridLayoutManager(recyclerView); } else { return getLinearLayoutManager(); } } public GridLayoutManager getGridLayoutManager(RecyclerView recyclerView) { final int columns = getRecyclerColumnCount(); GridLayoutManager gridLayoutManager = new GridLayoutManager(context, columns); GridLayoutManager.SpanSizeLookup spanSizeLookup = getSpanSizeLookup(columns); if (spanSizeLookup != null) { gridLayoutManager.setSpanSizeLookup(spanSizeLookup); } RecyclerView.ItemDecoration itemDecoration = getItemDecoration(); if (itemDecoration != null) { recyclerView.addItemDecoration(itemDecoration); } return gridLayoutManager; } public LinearLayoutManager getLinearLayoutManager() { LinearLayoutManager layoutManager = new LinearLayoutManager(context); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); return layoutManager; } public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int columns) { return new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { SectionAdapter adapter = getCurrentAdapter(); if (adapter != null) { int viewType = getCurrentAdapter().getItemViewType(position); if (viewType == SectionAdapter.VIEW_TYPE_HEADER) { return columns; } else { return 1; } } else { return 1; } } }; } public RecyclerView.ItemDecoration getItemDecoration() { return new GridSpacingDecoration(); } public int getRecyclerColumnCount() { return context.getResources().getInteger(R.integer.Grid_Columns); } protected 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 = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize(); 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); } } protected void onShuffleRequested() { if (Util.isOffline(context)) { DownloadService downloadService = getDownloadService(); if (downloadService == null) { return; } downloadService.setShufflePlayEnabled(true); context.openNowPlaying(); return; } View dialogView = context.getLayoutInflater().inflate(R.layout.shuffle_dialog, null); final EditText startYearBox = (EditText) dialogView.findViewById(R.id.start_year); final EditText endYearBox = (EditText) dialogView.findViewById(R.id.end_year); final EditText genreBox = (EditText) dialogView.findViewById(R.id.genre); final Button genreCombo = (Button) 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 = false; if (ServerInfo.checkServerVersion(context, "1.9.0")) { 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>(); 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()]), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int 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 || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } }); _useCombo = true; } else { genreCombo.setVisibility(View.GONE); } 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, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int 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.commit(); DownloadService downloadService = getDownloadService(); if (downloadService == null) { return; } downloadService.setShufflePlayEnabled(true); context.openNowPlaying(); } }).setNegativeButton(R.string.common_cancel, null); AlertDialog dialog = builder.create(); dialog.show(); } public void toggleStarred(Entry entry) { toggleStarred(entry, null); } public void toggleStarred(final Entry entry, final OnStarChange onStarChange) { final boolean starred = !entry.isStarred(); entry.setStarred(starred); if (onStarChange != null) { onStarChange.starChange(starred); } new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); if (entry.isDirectory() && Util.isTagBrowsing(context) && !Util.isOffline(context)) { if (entry.isAlbum()) { musicService.setStarred(null, null, Arrays.asList(entry), starred, null, context); } else { musicService.setStarred(null, Arrays.asList(entry), null, starred, null, context); } } else { musicService.setStarred(Arrays.asList(entry), null, null, starred, null, context); } new EntryInstanceUpdater(entry) { @Override public void update(Entry found) { found.setStarred(starred); } }.execute(); return null; } @Override protected void done(Void result) { // UpdateView Util.toast(context, context.getResources().getString( starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getTitle())); } @Override protected void error(Throwable error) { Log.w(TAG, "Failed to star", error); entry.setStarred(!starred); if (onStarChange != null) { onStarChange.starChange(!starred); } String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.starring_content_error, entry.getTitle()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } public void toggleStarred(final Artist entry) { final boolean starred = !entry.isStarred(); entry.setStarred(starred); new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); if (Util.isTagBrowsing(context) && !Util.isOffline(context)) { musicService.setStarred(null, Arrays.asList(new Entry(entry)), null, starred, null, context); } else { musicService.setStarred(Arrays.asList(new Entry(entry)), null, null, starred, null, context); } return null; } @Override protected void done(Void result) { // UpdateView Util.toast(context, context.getResources().getString( starred ? R.string.starring_content_starred : R.string.starring_content_unstarred, entry.getName())); } @Override protected void error(Throwable error) { Log.w(TAG, "Failed to star", error); entry.setStarred(!starred); String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.starring_content_error, entry.getName()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) { downloadRecursively(id, "", true, save, append, autoplay, shuffle, background); } protected 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); } protected void downloadPlaylist(final String id, final String name, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) { downloadRecursively(id, name, false, save, append, autoplay, shuffle, background); } protected 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) { downloadRecursively(id, name, isDirectory, save, append, autoplay, shuffle, background, false); } protected 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 (share != null) { root = share.getMusicDirectory(); } else if (isDirectory) { if (id != null) { root = getMusicDirectory(id, name, false, musicService, this); } else { root = musicService.getStarredList(context, 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<Entry>(); 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(); } protected void downloadRecursively(final List<Entry> albums, final boolean shuffle, final boolean append) { new RecursiveLoader(context) { @Override protected Boolean doInBackground() throws Throwable { musicService = MusicServiceFactory.getMusicService(context); if (shuffle) { Collections.shuffle(albums); } songs = new LinkedList<Entry>(); 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, false, false); if (!append) { transition = true; } } artistOverride = false; return transition; } }.execute(); } protected MusicDirectory getMusicDirectory(String id, String name, boolean refresh, MusicService service, ProgressListener listener) throws Exception { return getMusicDirectory(id, name, refresh, false, service, listener); } protected MusicDirectory getMusicDirectory(String id, String name, boolean refresh, boolean forceArtist, MusicService service, ProgressListener listener) throws Exception { if (Util.isTagBrowsing(context) && !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); } } protected void addToPlaylist(final List<Entry> songs) { 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<Playlist>(); 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() == true && playlist.getId().indexOf(".m3u") == -1 && !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, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int 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 || error instanceof ServerTooOldException) { 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 || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.updated_playlist_error, playlist.getName()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } protected void createNewPlaylist(final List<Entry> songs, final boolean getSuggestion) { View layout = context.getLayoutInflater().inflate(R.layout.save_playlist, null); final EditText playlistNameView = (EditText) layout.findViewById(R.id.save_playlist_name); final CheckBox overwriteCheckBox = (CheckBox) layout.findViewById(R.id.save_playlist_overwrite); if (getSuggestion) { String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null; if (playlistName != null) { playlistNameView.setText(playlistName); try { if (ServerInfo.checkServerVersion(context, "1.8.0") && Integer.parseInt(getDownloadService().getSuggestedPlaylistId()) != -1) { overwriteCheckBox.setChecked(true); overwriteCheckBox.setVisibility(View.VISIBLE); } } catch (Exception e) { Log.d(TAG, "Playlist id isn't a integer, probably MusicCabinet"); } } else { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); playlistNameView.setText(dateFormat.format(new Date())); } } else { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 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, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int 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, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int 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 || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.download_playlist_error) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } public 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 instanceof PodcastEpisode) { headers.add(R.string.details_podcast); details.add(song.getArtist()); headers.add(R.string.details_status); details.add(((PodcastEpisode) song).getStatus()); } else if (!song.isVideo()) { 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)); } if (song.getBookmark() != null) { headers.add(R.string.details_bookmark_position); details.add(Util.formatDuration(song.getBookmark().getPosition() / 1000)); } if (song.getRating() != 0) { headers.add(R.string.details_rating); details.add(song.getRating() + " stars"); } headers.add(R.string.details_starred); details.add(Util.formatBoolean(context, song.isStarred())); if (song instanceof PodcastEpisode) { headers.add(R.string.details_description); details.add(song.getAlbum()); } int title; if (song.isDirectory()) { title = R.string.details_title_album; } else if (song instanceof PodcastEpisode) { title = R.string.details_title_podcast; } else { title = R.string.details_title_song; } Util.showDetailsDialog(context, title, headers, details); } protected void playVideo(Entry entry) { if (entryExists(entry)) { playExternalPlayer(entry); } else { streamExternalPlayer(entry); } } protected void playWebView(Entry entry) { int maxBitrate = Util.getMaxVideoBitrate(context); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse( MusicServiceFactory.getMusicService(context).getVideoUrl(maxBitrate, context, entry.getId()))); startActivity(intent); } protected void playExternalPlayer(Entry entry) { if (!entryExists(entry)) { Util.toast(context, R.string.download_need_download); } else { DownloadFile check = new DownloadFile(context, entry, false); File file = check.getCompleteFile(); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "video/*"); intent.putExtra(Intent.EXTRA_TITLE, entry.getTitle()); List<ResolveInfo> intents = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (intents != null && intents.size() > 0) { startActivity(intent); } else { Util.toast(context, R.string.download_no_streaming_player); } } } protected void streamExternalPlayer(Entry entry) { String videoPlayerType = Util.getVideoPlayerType(context); if ("flash".equals(videoPlayerType)) { playWebView(entry); } else if ("hls".equals(videoPlayerType)) { streamExternalPlayer(entry, "hls"); } else if ("raw".equals(videoPlayerType)) { streamExternalPlayer(entry, "raw"); } else { streamExternalPlayer(entry, entry.getTranscodedSuffix()); } } protected void streamExternalPlayer(Entry entry, String format) { try { int maxBitrate = Util.getMaxVideoBitrate(context); Intent intent = new Intent(Intent.ACTION_VIEW); if ("hls".equals(format)) { intent.setDataAndType(Uri.parse( MusicServiceFactory.getMusicService(context).getHlsUrl(entry.getId(), maxBitrate, context)), "application/x-mpegURL"); } else { intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context) .getVideoStreamUrl(format, maxBitrate, context, entry.getId())), "video/*"); } intent.putExtra("title", entry.getTitle()); List<ResolveInfo> intents = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (intents != null && intents.size() > 0) { startActivity(intent); } else { Util.toast(context, R.string.download_no_streaming_player); } } catch (Exception error) { String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = error.getMessage(); } else { msg = context.getResources().getString(R.string.download_no_streaming_player) + " " + error.getMessage(); } Util.toast(context, msg, false); } } protected boolean entryExists(Entry entry) { DownloadFile check = new DownloadFile(context, entry, false); return check.isCompleteFileAvailable(); } public void deleteRecursively(Artist artist) { deleteRecursively(artist, FileUtil.getArtistDirectory(context, artist)); } public void deleteRecursively(Entry album) { deleteRecursively(album, FileUtil.getAlbumDirectory(context, album)); } public 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(); } public 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(); } public void showAlbumArtist(Entry entry) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); if (Util.isTagBrowsing(context)) { args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getArtistId()); } else { args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getParent()); } args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getArtist()); args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); fragment.setArguments(args); replaceFragment(fragment, true); } public void showArtist(Entry entry) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); if (Util.isTagBrowsing(context)) { args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getArtistId()); } else { if (entry.getGrandParent() == null) { args.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getParent()); } else { args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getGrandParent()); } } args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getArtist()); args.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true); fragment.setArguments(args); replaceFragment(fragment, true); } public void showAlbum(Entry entry) { SubsonicFragment fragment = new SelectDirectoryFragment(); Bundle args = new Bundle(); if (Util.isTagBrowsing(context)) { args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getAlbumId()); } else { args.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getParent()); } args.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getAlbum()); fragment.setArguments(args); replaceFragment(fragment, true); } public void createShare(final List<Entry> entries) { new LoadingTask<List<Share>>(context, true) { @Override protected List<Share> doInBackground() throws Throwable { List<String> ids = new ArrayList<String>(entries.size()); for (Entry entry : entries) { ids.add(entry.getId()); } MusicService musicService = MusicServiceFactory.getMusicService(context); return musicService.createShare(ids, null, 0L, context, this); } @Override protected void done(final List<Share> shares) { if (shares.size() > 0) { Share share = shares.get(0); shareExternal(share); } else { Util.toast(context, context.getResources().getString(R.string.playlist_error), false); } } @Override protected void error(Throwable error) { String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } public void shareExternal(Share share) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, share.getUrl()); context.startActivity(Intent.createChooser(intent, context.getResources().getString(R.string.share_via))); } public GestureDetector getGestureDetector() { return gestureScanner; } protected void playBookmark(List<Entry> songs, Entry song) { playBookmark(songs, song, null, null); } protected void playBookmark(final List<Entry> songs, final Entry song, final String playlistName, final String playlistId) { final Integer position = song.getBookmark().getPosition(); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.bookmark_resume_title) .setMessage(getResources().getString(R.string.bookmark_resume, song.getTitle(), Util.formatDuration(position / 1000))) .setPositiveButton(R.string.bookmark_action_resume, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { playNow(songs, song, position); } }).setNegativeButton(R.string.bookmark_action_start_over, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { final Bookmark oldBookmark = song.getBookmark(); song.setBookmark(null); new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.deleteBookmark(song, context, null); return null; } @Override protected void error(Throwable error) { song.setBookmark(oldBookmark); String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.bookmark_deleted_error, song.getTitle()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); playNow(songs, 0); } }); AlertDialog dialog = builder.create(); dialog.show(); } protected void playNow(List<Entry> entries) { playNow(entries, null, null); } protected 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 bookmark = null; for (Entry entry : songs) { if (entry.getBookmark() != null) { bookmark = entry; break; } } // If no bookmark found, just play from start if (bookmark == null) { playNow(songs, 0, playlistName, playlistId); } else { // If bookmark found, then give user choice to start from there or to start over playBookmark(songs, bookmark, playlistName, playlistId); } } }.execute(); } protected void playNow(List<Entry> entries, int position) { playNow(entries, position, null, null); } protected void playNow(List<Entry> entries, int position, String playlistName, String playlistId) { Entry selected = entries.isEmpty() ? null : entries.get(0); playNow(entries, selected, position, playlistName, playlistId); } protected void playNow(List<Entry> entries, Entry song, int position) { playNow(entries, song, position, null, null); } protected void playNow(final List<Entry> entries, final Entry song, final int position, final String playlistName, final String playlistId) { new LoadingTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { DownloadService downloadService = getDownloadService(); if (downloadService == null) { return null; } downloadService.clear(); downloadService.download(entries, false, true, true, false, entries.indexOf(song), position); downloadService.setSuggestedPlaylistName(playlistName, playlistId); return null; } @Override protected void done(Void result) { context.openNowPlaying(); } }.execute(); } protected void deleteBookmark(final MusicDirectory.Entry entry, final SectionAdapter adapter) { Util.confirmDialog(context, R.string.bookmark_delete_title, entry.getTitle(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final Bookmark oldBookmark = entry.getBookmark(); entry.setBookmark(null); new LoadingTask<Void>(context, false) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.deleteBookmark(entry, context, null); new EntryInstanceUpdater(entry) { @Override public void update(Entry found) { found.setBookmark(null); } }.execute(); return null; } @Override protected void done(Void result) { if (adapter != null) { adapter.removeItem(entry); } Util.toast(context, context.getResources().getString(R.string.bookmark_deleted, entry.getTitle())); } @Override protected void error(Throwable error) { entry.setBookmark(oldBookmark); String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources().getString(R.string.bookmark_deleted_error, entry.getTitle()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } }); } protected void setRating(Entry entry) { setRating(entry, null); } protected void setRating(final Entry entry, final OnRatingChange onRatingChange) { View layout = context.getLayoutInflater().inflate(R.layout.rating, null); final RatingBar ratingBar = (RatingBar) layout.findViewById(R.id.rating_bar); ratingBar.setRating((float) entry.getRating()); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(context.getResources().getString(R.string.rating_title, entry.getTitle())).setView(layout) .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { int rating = (int) ratingBar.getRating(); setRating(entry, rating, onRatingChange); } }).setNegativeButton(R.string.common_cancel, null); AlertDialog dialog = builder.create(); dialog.show(); } protected void setRating(Entry entry, int rating) { setRating(entry, rating, null); } protected void setRating(final Entry entry, final int rating, final OnRatingChange onRatingChange) { final int oldRating = entry.getRating(); entry.setRating(rating); if (onRatingChange != null) { onRatingChange.ratingChange(rating); } new SilentBackgroundTask<Void>(context) { @Override protected Void doInBackground() throws Throwable { MusicService musicService = MusicServiceFactory.getMusicService(context); musicService.setRating(entry, rating, context, null); new EntryInstanceUpdater(entry) { @Override public void update(Entry found) { found.setRating(rating); } }.execute(); return null; } @Override protected void done(Void result) { Util.toast(context, context.getResources().getString( rating > 0 ? R.string.rating_set_rating : R.string.rating_remove_rating, entry.getTitle())); } @Override protected void error(Throwable error) { entry.setRating(oldRating); if (onRatingChange != null) { onRatingChange.ratingChange(oldRating); } String msg; if (error instanceof OfflineException || error instanceof ServerTooOldException) { msg = getErrorMessage(error); } else { msg = context.getResources() .getString(rating > 0 ? R.string.rating_set_rating_failed : R.string.rating_remove_rating_failed, entry.getTitle()) + " " + getErrorMessage(error); } Util.toast(context, msg, false); } }.execute(); } public SectionAdapter getCurrentAdapter() { return null; } public void stopActionMode() { SectionAdapter adapter = getCurrentAdapter(); if (adapter != null) { adapter.stopActionMode(); } } protected void clearSelected() { if (getCurrentAdapter() != null) { getCurrentAdapter().clearSelected(); } } protected List<Entry> getSelectedEntries() { return getCurrentAdapter().getSelected(); } protected void playNow(final boolean shuffle, final boolean append) { playNow(shuffle, append, false); } protected void playNow(final boolean shuffle, final boolean append, final boolean playNext) { List<Entry> songs = getSelectedEntries(); if (!songs.isEmpty()) { download(songs, append, false, !append, playNext, shuffle); clearSelected(); } } protected void download(List<Entry> entries, boolean append, boolean save, boolean autoplay, boolean playNext, boolean shuffle) { download(entries, append, save, autoplay, playNext, shuffle, null, null); } protected void download(final List<Entry> entries, final boolean append, final boolean save, 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 && !save && autoplay && !playNext && !shuffle) { // Call playNow which goes through and tries to use bookmark 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, save, 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 (save) { Util.toast(context, context.getResources().getQuantityString( R.plurals.select_album_n_songs_downloading, songs.size(), songs.size())); } else if (append) { Util.toast(context, context.getResources() .getQuantityString(R.plurals.select_album_n_songs_added, songs.size(), songs.size())); } } }; executeOnValid(onValid); } protected void executeOnValid(RecursiveLoader onValid) { onValid.execute(); } protected void downloadBackground(final boolean save) { List<Entry> songs = getSelectedEntries(); if (!songs.isEmpty()) { downloadBackground(save, songs); } } protected 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, true); 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(); } protected void delete() { List<Entry> songs = getSelectedEntries(); if (!songs.isEmpty()) { DownloadService downloadService = getDownloadService(); if (downloadService != null) { downloadService.delete(songs); } } } protected abstract class EntryInstanceUpdater { private Entry entry; public EntryInstanceUpdater(Entry entry) { this.entry = entry; } public abstract void update(Entry found); public void execute() { DownloadService downloadService = getDownloadService(); if (downloadService != null && !entry.isDirectory()) { boolean serializeChanges = false; List<DownloadFile> downloadFiles = downloadService.getDownloads(); for (DownloadFile file : downloadFiles) { Entry check = file.getSong(); if (entry.getId().equals(check.getId())) { update(entry); serializeChanges = true; } } if (serializeChanges) { downloadService.serializeQueue(); } } Entry find = UpdateView.findEntry(entry); if (find != null) { update(find); } } } public abstract class OnRatingChange { abstract void ratingChange(int rating); } public abstract class OnStarChange { abstract void starChange(boolean starred); } public abstract class RecursiveLoader extends LoadingTask<Boolean> { protected MusicService musicService; protected static final int MAX_SONGS = 500; protected boolean playNowOverride = false; protected List<Entry> songs = new ArrayList<>(); public RecursiveLoader(Activity context) { super(context); musicService = MusicServiceFactory.getMusicService(context); } protected void getSongsRecursively(List<Entry> entry) throws Exception { getSongsRecursively(entry, false); } protected void getSongsRecursively(List<Entry> entry, boolean allowVideo) throws Exception { getSongsRecursively(entry, songs, allowVideo); } protected void getSongsRecursively(List<Entry> entry, List<Entry> songs) throws Exception { getSongsRecursively(entry, songs, false); } protected void getSongsRecursively(List<Entry> entry, List<Entry> songs, boolean allowVideo) throws Exception { MusicDirectory dir = new MusicDirectory(); dir.addChildren(entry); getSongsRecursively(dir, songs, allowVideo); } protected void getSongsRecursively(MusicDirectory parent, List<Entry> songs) throws Exception { getSongsRecursively(parent, songs, false); } protected void getSongsRecursively(MusicDirectory parent, List<Entry> songs, boolean allowVideo) throws Exception { if (songs.size() > MAX_SONGS) { return; } for (Entry dir : parent.getChildren(true, false)) { if (dir.getRating() == 1) { continue; } MusicDirectory musicDirectory; if (Util.isTagBrowsing(context) && !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); } for (Entry song : parent.getChildren(false, true)) { if ((!song.isVideo() || allowVideo) && song.getRating() != 1) { songs.add(song); } } } @Override protected void done(Boolean result) { warnIfStorageUnavailable(); if (playNowOverride) { playNow(songs); return; } if (result) { context.openNowPlaying(); } } } }