diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index 9fdd7025..7730a473 100644 Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.kt b/app/src/main/java/code/name/monkey/retromusic/Constants.kt index 452b2fa3..ae1f1da7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/Constants.kt +++ b/app/src/main/java/code/name/monkey/retromusic/Constants.kt @@ -108,6 +108,7 @@ const val INITIALIZED_BLACKLIST = "initialized_blacklist" const val ARTIST_SORT_ORDER = "artist_sort_order" const val ARTIST_ALBUM_SORT_ORDER = "artist_album_sort_order" const val ALBUM_SORT_ORDER = "album_sort_order" +const val PLAYLIST_SORT_ORDER = "playlist_sort_order" const val ALBUM_SONG_SORT_ORDER = "album_song_sort_order" const val ARTIST_SONG_SORT_ORDER = "artist_song_sort_order" const val ALBUM_GRID_SIZE = "album_grid_size" diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt index 25300b92..ac434622 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/LyricsActivity.kt @@ -17,8 +17,8 @@ package code.name.monkey.retromusic.activities import android.os.Bundle import android.view.Menu import android.view.MenuItem -import android.view.View import android.view.WindowManager +import androidx.core.view.ViewCompat import androidx.interpolator.view.animation.FastOutSlowInInterpolator import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper @@ -54,22 +54,17 @@ class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper. private fun buildContainerTransform(): MaterialContainerTransform { val transform = MaterialContainerTransform() transform.setAllContainerColors( - MaterialColors.getColor(findViewById(android.R.id.content), R.attr.colorSurface) + MaterialColors.getColor(findViewById(R.id.container), R.attr.colorSurface) ) - transform.addTarget(android.R.id.content) + transform.addTarget(R.id.container) transform.duration = 300 - transform.interpolator = FastOutSlowInInterpolator() - transform.pathMotion = MaterialArcMotion() return transform } override fun onCreate(savedInstanceState: Bundle?) { - findViewById(android.R.id.content).transitionName = "lyrics" - setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback()) - window.sharedElementEnterTransition = buildContainerTransform() - window.sharedElementReturnTransition = buildContainerTransform() super.onCreate(savedInstanceState) setContentView(R.layout.activity_lyrics) + ViewCompat.setTransitionName(container, "lyrics") setStatusbarColorAuto() setTaskDescriptionColorAuto() setNavigationbarColorAuto() diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt index eeb6426e..aaf52166 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/MainActivity.kt @@ -23,7 +23,32 @@ import android.provider.MediaStore import android.view.View import androidx.lifecycle.lifecycleScope import androidx.navigation.ui.NavigationUI -import code.name.monkey.retromusic.* +import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP +import code.name.monkey.retromusic.ALBUM_COVER_STYLE +import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM +import code.name.monkey.retromusic.BANNER_IMAGE_PATH +import code.name.monkey.retromusic.BLACK_THEME +import code.name.monkey.retromusic.CAROUSEL_EFFECT +import code.name.monkey.retromusic.CIRCULAR_ALBUM_ART +import code.name.monkey.retromusic.DESATURATED_COLOR +import code.name.monkey.retromusic.EXTRA_SONG_INFO +import code.name.monkey.retromusic.GENERAL_THEME +import code.name.monkey.retromusic.HOME_ARTIST_GRID_STYLE +import code.name.monkey.retromusic.KEEP_SCREEN_ON +import code.name.monkey.retromusic.LANGUAGE_NAME +import code.name.monkey.retromusic.LIBRARY_CATEGORIES +import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID +import code.name.monkey.retromusic.PROFILE_IMAGE_PATH +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.ROUND_CORNERS +import code.name.monkey.retromusic.TAB_TEXT_MODE +import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS +import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN +import code.name.monkey.retromusic.TOGGLE_GENRE +import code.name.monkey.retromusic.TOGGLE_HOME_BANNER +import code.name.monkey.retromusic.TOGGLE_SEPARATE_LINE +import code.name.monkey.retromusic.TOGGLE_VOLUME +import code.name.monkey.retromusic.USER_NAME import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity import code.name.monkey.retromusic.extensions.findNavController import code.name.monkey.retromusic.helper.MusicPlayerRemote @@ -32,6 +57,7 @@ import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.PlaylistSongsLoader import code.name.monkey.retromusic.service.MusicService +import code.name.monkey.retromusic.state.NowPlayingPanelState import code.name.monkey.retromusic.util.AppRater import code.name.monkey.retromusic.util.PreferenceUtil import kotlinx.coroutines.Dispatchers.IO @@ -94,7 +120,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis intent.getBooleanExtra(EXPAND_PANEL, false) && PreferenceUtil.isExpandPanel ) { - expandPanel() + libraryViewModel.setPanelState(NowPlayingPanelState.EXPAND) intent.removeExtra(EXPAND_PANEL) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt index 009912af..c6d7e501 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsSlidingMusicPanelActivity.kt @@ -29,7 +29,11 @@ import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.RetroBottomSheetBehavior -import code.name.monkey.retromusic.extensions.* +import code.name.monkey.retromusic.extensions.hide +import code.name.monkey.retromusic.extensions.peekHeightAnimate +import code.name.monkey.retromusic.extensions.show +import code.name.monkey.retromusic.extensions.translateXAnimate +import code.name.monkey.retromusic.extensions.whichFragment import code.name.monkey.retromusic.fragments.LibraryViewModel import code.name.monkey.retromusic.fragments.MiniPlayerFragment import code.name.monkey.retromusic.fragments.NowPlayingScreen @@ -118,6 +122,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY) dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f)) dimBackground.setOnClickListener { + println("dimBackground") libraryViewModel.setPanelState(COLLAPSED_WITH) } } @@ -209,6 +214,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) + println("onServiceConnected") if (bottomNavigationView.isVisible) { libraryViewModel.setPanelState(COLLAPSED_WITH) } else { @@ -225,6 +231,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { libraryViewModel.setPanelState(HIDE) } else { if (bottomNavigationView.isVisible) { + println("onQueueChanged") libraryViewModel.setPanelState(COLLAPSED_WITH) } else { libraryViewModel.setPanelState(COLLAPSED_WITHOUT) @@ -339,6 +346,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() { EXPAND -> { println("EXPAND") expandPanel() + bottomNavigationView.translateXAnimate(150f) } HIDE -> { println("HIDE") diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt index d994c824..f6ca4179 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/album/AlbumCoverPagerAdapter.kt @@ -14,18 +14,16 @@ */ package code.name.monkey.retromusic.adapter.album -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView -import androidx.core.app.ActivityOptionsCompat +import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.lifecycleScope import code.name.monkey.retromusic.R -import code.name.monkey.retromusic.activities.LyricsActivity import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.glide.RetroMusicColoredTarget @@ -105,15 +103,10 @@ class AlbumCoverPagerAdapter( savedInstanceState: Bundle? ): View? { val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) + ViewCompat.setTransitionName(view, "lyrics") albumCover = view.findViewById(R.id.player_image) - albumCover.setOnClickListener { - val intent = Intent(requireContext(), LyricsActivity::class.java) - val activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation( - requireActivity(), - it, - "lyrics" - ) - startActivity(intent, activityOptions.toBundle()) + view.setOnClickListener { + showLyricsDialog() } return view } diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt index ddfe8c0c..df14d37d 100755 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/playlist/PlaylistAdapter.kt @@ -24,12 +24,10 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.PopupMenu -import androidx.core.os.bundleOf +import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentActivity -import androidx.navigation.findNavController import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.TintHelper -import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder @@ -42,6 +40,7 @@ import code.name.monkey.retromusic.extensions.show import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.interfaces.ICabHolder +import code.name.monkey.retromusic.interfaces.IPlaylistClickListener import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.PlaylistSongsLoader @@ -53,7 +52,8 @@ class PlaylistAdapter( private val activity: FragmentActivity, var dataSet: List, private var itemLayoutRes: Int, - ICabHolder: ICabHolder? + ICabHolder: ICabHolder?, + private val listener: IPlaylistClickListener ) : AbsMultiSelectAdapter( activity, ICabHolder, @@ -172,10 +172,8 @@ class PlaylistAdapter( if (isInQuickSelectMode) { toggleChecked(layoutPosition) } else { - activity.findNavController(R.id.fragment_container).navigate( - R.id.playlistDetailsFragment, - bundleOf(EXTRA_PLAYLIST to dataSet[layoutPosition]) - ) + ViewCompat.setTransitionName(itemView, "playlist") + listener.onPlaylistClick(dataSet[layoutPosition], itemView) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt index 93ac92f8..a480c7a3 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveSongFromPlaylistDialog.kt @@ -26,7 +26,6 @@ import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.fragments.LibraryViewModel -import code.name.monkey.retromusic.fragments.ReloadType.Playlists import org.koin.androidx.viewmodel.ext.android.sharedViewModel class RemoveSongFromPlaylistDialog : DialogFragment() { @@ -74,7 +73,6 @@ class RemoveSongFromPlaylistDialog : DialogFragment() { .setMessage(pair.second) .setPositiveButton(R.string.remove_action) { _, _ -> libraryViewModel.deleteSongsInPlaylist(songs) - libraryViewModel.forceReload(Playlists) } .setNegativeButton(android.R.string.cancel, null) .create() diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt index 5854a750..96b28733 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/LibraryViewModel.kt @@ -222,8 +222,11 @@ class LibraryViewModel( repository.renameRoomPlaylist(playListId, name) } - fun deleteSongsInPlaylist(songs: List) = viewModelScope.launch(IO) { - repository.deleteSongsInPlaylist(songs) + fun deleteSongsInPlaylist(songs: List) { + viewModelScope.launch(IO) { + repository.deleteSongsInPlaylist(songs) + forceReload(Playlists) + } } fun deleteSongsFromPlaylist(playlists: List) = viewModelScope.launch(IO) { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt index c3f85d72..0dfa0ba7 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/albums/AlbumDetailsViewModel.kt @@ -15,8 +15,10 @@ package code.name.monkey.retromusic.fragments.albums import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData +import androidx.lifecycle.viewModelScope import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Artist @@ -24,16 +26,26 @@ import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.repository.RealRepository import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch class AlbumDetailsViewModel( private val repository: RealRepository, private val albumId: Long ) : ViewModel(), IMusicServiceEventListener { + private val albumDetails = MutableLiveData() - fun getAlbum(): LiveData = liveData(IO) { - emit(repository.albumByIdAsync(albumId)) + init { + fetchAlbum() } + private fun fetchAlbum() { + viewModelScope.launch(IO) { + albumDetails.postValue(repository.albumByIdAsync(albumId)) + } + } + + fun getAlbum(): LiveData = albumDetails + fun getArtist(artistId: Long): LiveData = liveData(IO) { val artist = repository.artistById(artistId) emit(artist) @@ -51,6 +63,7 @@ class AlbumDetailsViewModel( } override fun onMediaStoreChanged() { + fetchAlbum() } override fun onServiceConnected() {} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt index d63b0e1b..c9f12a70 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsFragment.kt @@ -94,16 +94,14 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setHasOptionsMenu(true) - libraryViewModel.setPanelState(NowPlayingPanelState.COLLAPSED_WITHOUT) - mainActivity.setSupportActionBar(toolbar) + libraryViewModel.setPanelState(NowPlayingPanelState.COLLAPSED_WITHOUT) toolbar.title = null setupRecyclerView() ViewCompat.setTransitionName(container, "artist") - postponeEnterTransition() detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer { startPostponedEnterTransition() diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt index a871069f..dbb59866 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/artists/ArtistDetailsViewModel.kt @@ -15,25 +15,36 @@ package code.name.monkey.retromusic.fragments.artists import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData +import androidx.lifecycle.viewModelScope import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.network.Result import code.name.monkey.retromusic.network.model.LastFmArtist import code.name.monkey.retromusic.repository.RealRepository import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch class ArtistDetailsViewModel( private val realRepository: RealRepository, private val artistId: Long ) : ViewModel(), IMusicServiceEventListener { + private val artistDetails = MutableLiveData() - fun getArtist(): LiveData = liveData(IO) { - val artist = realRepository.artistById(artistId) - emit(artist) + init { + fetchArtist() } + private fun fetchArtist() { + viewModelScope.launch(IO) { + artistDetails.postValue(realRepository.artistById(artistId)) + } + } + + fun getArtist(): LiveData = artistDetails + fun getArtistInfo( name: String, lang: String?, @@ -45,7 +56,7 @@ class ArtistDetailsViewModel( } override fun onMediaStoreChanged() { - getArtist() + fetchArtist() } override fun onServiceConnected() {} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt index cf46337d..7a03ab2a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewCustomGridSizeFragment.kt @@ -73,6 +73,7 @@ abstract class AbsRecyclerViewCustomGridSizeFragment fun setAndSaveSortOrder(sortOrder: String) { this.sortOrder = sortOrder + println(sortOrder) saveSortOrder(sortOrder) setSortOrder(sortOrder) } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt index 1d06263b..86243a13 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/base/AbsRecyclerViewFragment.kt @@ -61,6 +61,7 @@ abstract class AbsRecyclerViewFragment, LM : Recycle override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + println("AbsRecyclerViewFragment") libraryViewModel.setPanelState(NowPlayingPanelState.COLLAPSED_WITH) mainActivity.setSupportActionBar(toolbar) mainActivity.supportActionBar?.title = null diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java index e0769e8f..5e03ad76 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/folder/FoldersFragment.java @@ -14,8 +14,6 @@ package code.name.monkey.retromusic.fragments.folder; -import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; - import android.app.Dialog; import android.content.Context; import android.media.MediaScannerConnection; @@ -34,6 +32,7 @@ import android.webkit.MimeTypeMap; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; @@ -43,6 +42,23 @@ import androidx.loader.content.Loader; import androidx.navigation.Navigation; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + +import com.afollestad.materialcab.MaterialCab; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.snackbar.Snackbar; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + import code.name.monkey.appthemehelper.ThemeStore; import code.name.monkey.appthemehelper.util.ATHUtil; import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; @@ -67,754 +83,744 @@ import code.name.monkey.retromusic.util.RetroColorUtil; import code.name.monkey.retromusic.util.ThemedFastScroller; import code.name.monkey.retromusic.views.BreadCrumbLayout; import code.name.monkey.retromusic.views.ScrollingViewOnApplyWindowInsetsListener; -import com.afollestad.materialcab.MaterialCab; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.snackbar.Snackbar; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; import me.zhanghai.android.fastscroll.FastScroller; -import org.jetbrains.annotations.NotNull; + +import static code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor; public class FoldersFragment extends AbsMainActivityFragment - implements IMainActivityFragmentCallbacks, + implements IMainActivityFragmentCallbacks, ICabHolder, BreadCrumbLayout.SelectionCallback, ICallbacks, LoaderManager.LoaderCallbacks> { - public static final String TAG = FoldersFragment.class.getSimpleName(); - public static final FileFilter AUDIO_FILE_FILTER = - file -> - !file.isHidden() - && (file.isDirectory() - || FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) - || FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) - || FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); + public static final String TAG = FoldersFragment.class.getSimpleName(); + public static final FileFilter AUDIO_FILE_FILTER = + file -> + !file.isHidden() + && (file.isDirectory() + || FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) + || FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) + || FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); - private static final String CRUMBS = "crumbs"; - private static final int LOADER_ID = 5; - private SongFileAdapter adapter; - private Toolbar toolbar; - private TextView appNameText; - private BreadCrumbLayout breadCrumbs; - private MaterialCab cab; - private View coordinatorLayout; - private View empty; - private TextView emojiText; - private Comparator fileComparator = - (lhs, rhs) -> { - if (lhs.isDirectory() && !rhs.isDirectory()) { - return -1; - } else if (!lhs.isDirectory() && rhs.isDirectory()) { - return 1; - } else { - return lhs.getName().compareToIgnoreCase(rhs.getName()); - } - }; - private RecyclerView recyclerView; - - public FoldersFragment() { - super(R.layout.fragment_folder); - } - - public static File getDefaultStartDirectory() { - File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); - File startFolder; - if (musicDir.exists() && musicDir.isDirectory()) { - startFolder = musicDir; - } else { - File externalStorage = Environment.getExternalStorageDirectory(); - if (externalStorage.exists() && externalStorage.isDirectory()) { - startFolder = externalStorage; - } else { - startFolder = new File("/"); // root - } - } - return startFolder; - } - - private static File tryGetCanonicalFile(File file) { - try { - return file.getCanonicalFile(); - } catch (IOException e) { - e.printStackTrace(); - return file; - } - } - - @NonNull - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_folder, container, false); - initViews(view); - return view; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - getLibraryViewModel().setPanelState(NowPlayingPanelState.COLLAPSED_WITH); - getMainActivity().setSupportActionBar(toolbar); - getMainActivity().getSupportActionBar().setTitle(null); - setStatusBarColorAuto(view); - setUpAppbarColor(); - setUpBreadCrumbs(); - setUpRecyclerView(); - setUpAdapter(); - setUpTitle(); - } - - private void setUpTitle() { - toolbar.setNavigationOnClickListener( - v -> Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions())); - int color = ThemeStore.Companion.accentColor(requireContext()); - String hexColor = String.format("#%06X", 0xFFFFFF & color); - Spanned appName = - HtmlCompat.fromHtml( - "Retro Music", - HtmlCompat.FROM_HTML_MODE_COMPACT); - appNameText.setText(appName); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setHasOptionsMenu(true); - if (savedInstanceState == null) { - setCrumb( - new BreadCrumbLayout.Crumb( - FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), - true); - } else { - breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); - LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); - } - } - - @Override - public void onPause() { - super.onPause(); - saveScrollPosition(); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - if (breadCrumbs != null) { - outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); - } - } - - @Override - public boolean handleBackPress() { - if (cab != null && cab.isActive()) { - cab.finish(); - return true; - } - if (breadCrumbs != null && breadCrumbs.popHistory()) { - setCrumb(breadCrumbs.lastHistory(), false); - return true; - } - return false; - } - - @NonNull - @Override - public Loader> onCreateLoader(int id, Bundle args) { - return new AsyncFileLoader(this); - } - - @Override - public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { - setCrumb(crumb, true); - } - - @Override - public void onFileMenuClicked(final File file, @NotNull View view) { - PopupMenu popupMenu = new PopupMenu(getActivity(), view); - if (file.isDirectory()) { - popupMenu.inflate(R.menu.menu_item_directory); - popupMenu.setOnMenuItemClickListener( - item -> { - final int itemId = item.getItemId(); - switch (itemId) { - case R.id.action_play_next: - case R.id.action_add_to_current_playing: - case R.id.action_add_to_playlist: - case R.id.action_delete_from_device: - new ListSongsAsyncTask( - getActivity(), - null, - (songs, extra) -> { - if (!songs.isEmpty()) { - SongsMenuHelper.INSTANCE.handleMenuClick( - requireActivity(), songs, itemId); - } - }) - .execute( - new ListSongsAsyncTask.LoadingInfo( - toList(file), AUDIO_FILE_FILTER, getFileComparator())); - return true; - case R.id.action_set_as_start_directory: - PreferenceUtil.INSTANCE.setStartDirectory(file); - Toast.makeText( - getActivity(), - String.format(getString(R.string.new_start_directory), file.getPath()), - Toast.LENGTH_SHORT) - .show(); - return true; - case R.id.action_scan: - new ListPathsAsyncTask(getActivity(), this::scanPaths) - .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); - return true; - } - return false; - }); - } else { - popupMenu.inflate(R.menu.menu_item_file); - popupMenu.setOnMenuItemClickListener( - item -> { - final int itemId = item.getItemId(); - switch (itemId) { - case R.id.action_play_next: - case R.id.action_add_to_current_playing: - case R.id.action_add_to_playlist: - case R.id.action_go_to_album: - case R.id.action_go_to_artist: - case R.id.action_share: - case R.id.action_tag_editor: - case R.id.action_details: - case R.id.action_set_as_ringtone: - case R.id.action_delete_from_device: - new ListSongsAsyncTask( - getActivity(), - null, - (songs, extra) -> - SongMenuHelper.INSTANCE.handleMenuClick( - requireActivity(), songs.get(0), itemId)) - .execute( - new ListSongsAsyncTask.LoadingInfo( - toList(file), AUDIO_FILE_FILTER, getFileComparator())); - return true; - case R.id.action_scan: - new ListPathsAsyncTask(getActivity(), this::scanPaths) - .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); - return true; - } - return false; - }); - } - popupMenu.show(); - } - - @Override - public void onFileSelected(@NotNull File file) { - file = tryGetCanonicalFile(file); // important as we compare the path value later - if (file.isDirectory()) { - setCrumb(new BreadCrumbLayout.Crumb(file), true); - } else { - FileFilter fileFilter = - pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname); - new ListSongsAsyncTask( - getActivity(), - file, - (songs, extra) -> { - File file1 = (File) extra; - int startIndex = -1; - for (int i = 0; i < songs.size(); i++) { - if (file1 - .getPath() - .equals(songs.get(i).getData())) { // path is already canonical here - startIndex = i; - break; - } - } - if (startIndex > -1) { - MusicPlayerRemote.openQueue(songs, startIndex, true); + private static final String CRUMBS = "crumbs"; + private static final int LOADER_ID = 5; + private SongFileAdapter adapter; + private Toolbar toolbar; + private TextView appNameText; + private BreadCrumbLayout breadCrumbs; + private MaterialCab cab; + private View coordinatorLayout; + private View empty; + private TextView emojiText; + private Comparator fileComparator = + (lhs, rhs) -> { + if (lhs.isDirectory() && !rhs.isDirectory()) { + return -1; + } else if (!lhs.isDirectory() && rhs.isDirectory()) { + return 1; } else { - final File finalFile = file1; - Snackbar.make( - coordinatorLayout, - Html.fromHtml( - String.format( - getString(R.string.not_listed_in_media_store), file1.getName())), - Snackbar.LENGTH_LONG) - .setAction( - R.string.action_scan, - v -> - new ListPathsAsyncTask(requireActivity(), this::scanPaths) - .execute( - new ListPathsAsyncTask.LoadingInfo( - finalFile, AUDIO_FILE_FILTER))) - .setActionTextColor(ThemeStore.Companion.accentColor(requireActivity())) - .show(); + return lhs.getName().compareToIgnoreCase(rhs.getName()); } - }) - .execute( - new ListSongsAsyncTask.LoadingInfo( - toList(file.getParentFile()), fileFilter, getFileComparator())); + }; + private RecyclerView recyclerView; + + public FoldersFragment() { + super(R.layout.fragment_folder); } - } - @Override - public void onLoadFinished(@NonNull Loader> loader, List data) { - updateAdapter(data); - } + public static File getDefaultStartDirectory() { + File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + File startFolder; + if (musicDir.exists() && musicDir.isDirectory()) { + startFolder = musicDir; + } else { + File externalStorage = Environment.getExternalStorageDirectory(); + if (externalStorage.exists() && externalStorage.isDirectory()) { + startFolder = externalStorage; + } else { + startFolder = new File("/"); // root + } + } + return startFolder; + } - @Override - public void onLoaderReset(@NonNull Loader> loader) { - updateAdapter(new LinkedList()); - } + private static File tryGetCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + e.printStackTrace(); + return file; + } + } - @Override - public void onMultipleItemAction(MenuItem item, @NotNull ArrayList files) { - final int itemId = item.getItemId(); - new ListSongsAsyncTask( - getActivity(), - null, - (songs, extra) -> - SongsMenuHelper.INSTANCE.handleMenuClick(requireActivity(), songs, itemId)) - .execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator())); - } + @NonNull + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_folder, container, false); + initViews(view); + return view; + } - @Override - public void onPrepareOptionsMenu(@NonNull Menu menu) { - super.onPrepareOptionsMenu(menu); - ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar); - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + getMainActivity().addMusicServiceEventListener(getLibraryViewModel()); + getLibraryViewModel().setPanelState(NowPlayingPanelState.COLLAPSED_WITH); + getMainActivity().setSupportActionBar(toolbar); + getMainActivity().getSupportActionBar().setTitle(null); + setStatusBarColorAuto(view); + setUpAppbarColor(); + setUpBreadCrumbs(); + setUpRecyclerView(); + setUpAdapter(); + setUpTitle(); + } - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - menu.add(0, R.id.action_scan, 0, R.string.scan_media) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - menu.add(0, R.id.action_go_to_start_directory, 1, R.string.action_go_to_start_directory) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - menu.removeItem(R.id.action_grid_size); - menu.removeItem(R.id.action_layout_type); - menu.removeItem(R.id.action_sort_order); - ToolbarContentTintHelper.handleOnCreateOptionsMenu( - requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar)); - } + private void setUpTitle() { + toolbar.setNavigationOnClickListener( + v -> Navigation.findNavController(v).navigate(R.id.searchFragment, null, getNavOptions())); + int color = ThemeStore.Companion.accentColor(requireContext()); + String hexColor = String.format("#%06X", 0xFFFFFF & color); + Spanned appName = + HtmlCompat.fromHtml( + "Retro Music", + HtmlCompat.FROM_HTML_MODE_COMPACT); + appNameText.setText(appName); + } - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.action_go_to_start_directory: - setCrumb( - new BreadCrumbLayout.Crumb( - tryGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), - true); - return true; - case R.id.action_scan: + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + if (savedInstanceState == null) { + setCrumb( + new BreadCrumbLayout.Crumb( + FileUtil.safeGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), + true); + } else { + breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); + LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this); + } + } + + @Override + public void onPause() { + super.onPause(); + saveScrollPosition(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + if (breadCrumbs != null) { + outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); + } + } + + @Override + public boolean handleBackPress() { + if (cab != null && cab.isActive()) { + cab.finish(); + return true; + } + if (breadCrumbs != null && breadCrumbs.popHistory()) { + setCrumb(breadCrumbs.lastHistory(), false); + return true; + } + return false; + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new AsyncFileLoader(this); + } + + @Override + public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { + setCrumb(crumb, true); + } + + @Override + public void onFileMenuClicked(final File file, @NotNull View view) { + PopupMenu popupMenu = new PopupMenu(getActivity(), view); + if (file.isDirectory()) { + popupMenu.inflate(R.menu.menu_item_directory); + popupMenu.setOnMenuItemClickListener( + item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_delete_from_device: + new ListSongsAsyncTask( + getActivity(), + null, + (songs, extra) -> { + if (!songs.isEmpty()) { + SongsMenuHelper.INSTANCE.handleMenuClick( + requireActivity(), songs, itemId); + } + }) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_set_as_start_directory: + PreferenceUtil.INSTANCE.setStartDirectory(file); + Toast.makeText( + getActivity(), + String.format(getString(R.string.new_start_directory), file.getPath()), + Toast.LENGTH_SHORT) + .show(); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), this::scanPaths) + .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); + } else { + popupMenu.inflate(R.menu.menu_item_file); + popupMenu.setOnMenuItemClickListener( + item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_go_to_album: + case R.id.action_go_to_artist: + case R.id.action_share: + case R.id.action_tag_editor: + case R.id.action_details: + case R.id.action_set_as_ringtone: + case R.id.action_delete_from_device: + new ListSongsAsyncTask( + getActivity(), + null, + (songs, extra) -> + SongMenuHelper.INSTANCE.handleMenuClick( + requireActivity(), songs.get(0), itemId)) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), this::scanPaths) + .execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); + } + popupMenu.show(); + } + + @Override + public void onFileSelected(@NotNull File file) { + file = tryGetCanonicalFile(file); // important as we compare the path value later + if (file.isDirectory()) { + setCrumb(new BreadCrumbLayout.Crumb(file), true); + } else { + FileFilter fileFilter = + pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname); + new ListSongsAsyncTask( + getActivity(), + file, + (songs, extra) -> { + File file1 = (File) extra; + int startIndex = -1; + for (int i = 0; i < songs.size(); i++) { + if (file1 + .getPath() + .equals(songs.get(i).getData())) { // path is already canonical here + startIndex = i; + break; + } + } + if (startIndex > -1) { + MusicPlayerRemote.openQueue(songs, startIndex, true); + } else { + final File finalFile = file1; + Snackbar.make( + coordinatorLayout, + Html.fromHtml( + String.format( + getString(R.string.not_listed_in_media_store), file1.getName())), + Snackbar.LENGTH_LONG) + .setAction( + R.string.action_scan, + v -> + new ListPathsAsyncTask(requireActivity(), this::scanPaths) + .execute( + new ListPathsAsyncTask.LoadingInfo( + finalFile, AUDIO_FILE_FILTER))) + .setActionTextColor(ThemeStore.Companion.accentColor(requireActivity())) + .show(); + } + }) + .execute( + new ListSongsAsyncTask.LoadingInfo( + toList(file.getParentFile()), fileFilter, getFileComparator())); + } + } + + @Override + public void onLoadFinished(@NonNull Loader> loader, List data) { + updateAdapter(data); + } + + @Override + public void onLoaderReset(@NonNull Loader> loader) { + updateAdapter(new LinkedList()); + } + + @Override + public void onMultipleItemAction(MenuItem item, @NotNull ArrayList files) { + final int itemId = item.getItemId(); + new ListSongsAsyncTask( + getActivity(), + null, + (songs, extra) -> + SongsMenuHelper.INSTANCE.handleMenuClick(requireActivity(), songs, itemId)) + .execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator())); + } + + @Override + public void onPrepareOptionsMenu(@NonNull Menu menu) { + super.onPrepareOptionsMenu(menu); + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(requireActivity(), toolbar); + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.add(0, R.id.action_scan, 0, R.string.scan_media) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, R.id.action_go_to_start_directory, 1, R.string.action_go_to_start_directory) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.removeItem(R.id.action_grid_size); + menu.removeItem(R.id.action_layout_type); + menu.removeItem(R.id.action_sort_order); + ToolbarContentTintHelper.handleOnCreateOptionsMenu( + requireContext(), toolbar, menu, getToolbarBackgroundColor(toolbar)); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_go_to_start_directory: + setCrumb( + new BreadCrumbLayout.Crumb( + tryGetCanonicalFile(PreferenceUtil.INSTANCE.getStartDirectory())), + true); + return true; + case R.id.action_scan: + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null) { + //noinspection Convert2MethodRef + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)) + .execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER)); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onQueueChanged() { + super.onQueueChanged(); + checkForPadding(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + checkForPadding(); + } + + @NonNull + @Override + public MaterialCab openCab(int menuRes, @NotNull MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) { + cab.finish(); + } + cab = + new MaterialCab(getMainActivity(), R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close) + .setBackgroundColor( + RetroColorUtil.shiftBackgroundColorForLightText( + ATHUtil.INSTANCE.resolveColor(requireContext(), R.attr.colorSurface))) + .start(callback); + return cab; + } + + private void checkForPadding() { + final int count = adapter.getItemCount(); + final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams(); + params.bottomMargin = + count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() + ? DensityUtil.dip2px(requireContext(), 104f) + : DensityUtil.dip2px(requireContext(), 54f); + } + + private void checkIsEmpty() { + emojiText.setText(getEmojiByUnicode(0x1F631)); + if (empty != null) { + empty.setVisibility( + adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + } + + @Nullable + private BreadCrumbLayout.Crumb getActiveCrumb() { + return breadCrumbs != null && breadCrumbs.size() > 0 + ? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) + : null; + } + + private String getEmojiByUnicode(int unicode) { + return new String(Character.toChars(unicode)); + } + + private Comparator getFileComparator() { + return fileComparator; + } + + private void initViews(View view) { + coordinatorLayout = view.findViewById(R.id.coordinatorLayout); + recyclerView = view.findViewById(R.id.recyclerView); + breadCrumbs = view.findViewById(R.id.breadCrumbs); + empty = view.findViewById(android.R.id.empty); + emojiText = view.findViewById(R.id.emptyEmoji); + toolbar = view.findViewById(R.id.toolbar); + appNameText = view.findViewById(R.id.appNameText); + } + + private void saveScrollPosition() { BreadCrumbLayout.Crumb crumb = getActiveCrumb(); if (crumb != null) { - //noinspection Convert2MethodRef - new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)) - .execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER)); + crumb.setScrollPosition( + ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); } - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onQueueChanged() { - super.onQueueChanged(); - checkForPadding(); - } - - @Override - public void onServiceConnected() { - super.onServiceConnected(); - checkForPadding(); - } - - @NonNull - @Override - public MaterialCab openCab(int menuRes, @NotNull MaterialCab.Callback callback) { - if (cab != null && cab.isActive()) { - cab.finish(); - } - cab = - new MaterialCab(getMainActivity(), R.id.cab_stub) - .setMenu(menuRes) - .setCloseDrawableRes(R.drawable.ic_close) - .setBackgroundColor( - RetroColorUtil.shiftBackgroundColorForLightText( - ATHUtil.INSTANCE.resolveColor(requireContext(), R.attr.colorSurface))) - .start(callback); - return cab; - } - - private void checkForPadding() { - final int count = adapter.getItemCount(); - final MarginLayoutParams params = (MarginLayoutParams) coordinatorLayout.getLayoutParams(); - params.bottomMargin = - count > 0 && !MusicPlayerRemote.getPlayingQueue().isEmpty() - ? DensityUtil.dip2px(requireContext(), 104f) - : DensityUtil.dip2px(requireContext(), 54f); - } - - private void checkIsEmpty() { - emojiText.setText(getEmojiByUnicode(0x1F631)); - if (empty != null) { - empty.setVisibility( - adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); - } - } - - @Nullable - private BreadCrumbLayout.Crumb getActiveCrumb() { - return breadCrumbs != null && breadCrumbs.size() > 0 - ? breadCrumbs.getCrumb(breadCrumbs.getActiveIndex()) - : null; - } - - private String getEmojiByUnicode(int unicode) { - return new String(Character.toChars(unicode)); - } - - private Comparator getFileComparator() { - return fileComparator; - } - - private void initViews(View view) { - coordinatorLayout = view.findViewById(R.id.coordinatorLayout); - recyclerView = view.findViewById(R.id.recyclerView); - breadCrumbs = view.findViewById(R.id.breadCrumbs); - empty = view.findViewById(android.R.id.empty); - emojiText = view.findViewById(R.id.emptyEmoji); - toolbar = view.findViewById(R.id.toolbar); - appNameText = view.findViewById(R.id.appNameText); - } - - private void saveScrollPosition() { - BreadCrumbLayout.Crumb crumb = getActiveCrumb(); - if (crumb != null) { - crumb.setScrollPosition( - ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); - } - } - - private void scanPaths(@Nullable String[] toBeScanned) { - if (getActivity() == null) { - return; - } - if (toBeScanned == null || toBeScanned.length < 1) { - Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); - } else { - MediaScannerConnection.scanFile( - getActivity().getApplicationContext(), - toBeScanned, - null, - new UpdateToastMediaScannerCompletionListener(getActivity(), toBeScanned)); - } - } - - private void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { - if (crumb == null) { - return; - } - saveScrollPosition(); - breadCrumbs.setActiveOrAdd(crumb, false); - if (addToHistory) { - breadCrumbs.addHistory(crumb); - } - LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); - } - - private void setUpAdapter() { - adapter = - new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this); - adapter.registerAdapterDataObserver( - new RecyclerView.AdapterDataObserver() { - @Override - public void onChanged() { - super.onChanged(); - checkIsEmpty(); - checkForPadding(); - } - }); - recyclerView.setAdapter(adapter); - checkIsEmpty(); - } - - private void setUpAppbarColor() { - breadCrumbs.setActivatedContentColor( - ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary)); - breadCrumbs.setDeactivatedContentColor( - ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary)); - } - - private void setUpBreadCrumbs() { - breadCrumbs.setCallback(this); - } - - private void setUpRecyclerView() { - recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView); - recyclerView.setOnApplyWindowInsetsListener( - new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller)); - } - - private ArrayList toList(File file) { - ArrayList files = new ArrayList<>(1); - files.add(file); - return files; - } - - private void updateAdapter(@NonNull List files) { - adapter.swapDataSet(files); - BreadCrumbLayout.Crumb crumb = getActiveCrumb(); - if (crumb != null && recyclerView != null) { - ((LinearLayoutManager) recyclerView.getLayoutManager()) - .scrollToPositionWithOffset(crumb.getScrollPosition(), 0); - } - } - - public static class ListPathsAsyncTask - extends ListingFilesDialogAsyncTask { - - private WeakReference onPathsListedCallbackWeakReference; - - public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { - super(context); - onPathsListedCallbackWeakReference = new WeakReference<>(callback); } - @Override - protected String[] doInBackground(LoadingInfo... params) { - try { - if (isCancelled() || checkCallbackReference() == null) { - return null; + private void scanPaths(@Nullable String[] toBeScanned) { + if (getActivity() == null) { + return; } - - LoadingInfo info = params[0]; - - final String[] paths; - - if (info.file.isDirectory()) { - List files = FileUtil.listFilesDeep(info.file, info.fileFilter); - - if (isCancelled() || checkCallbackReference() == null) { - return null; - } - - paths = new String[files.size()]; - for (int i = 0; i < files.size(); i++) { - File f = files.get(i); - paths[i] = FileUtil.safeGetCanonicalPath(f); - - if (isCancelled() || checkCallbackReference() == null) { - return null; - } - } + if (toBeScanned == null || toBeScanned.length < 1) { + Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); } else { - paths = new String[1]; - paths[0] = info.file.getPath(); + MediaScannerConnection.scanFile( + getActivity().getApplicationContext(), + toBeScanned, + null, + new UpdateToastMediaScannerCompletionListener(getActivity(), toBeScanned)); } - - return paths; - } catch (Exception e) { - e.printStackTrace(); - cancel(false); - return null; - } } - @Override - protected void onPostExecute(String[] paths) { - super.onPostExecute(paths); - OnPathsListedCallback callback = checkCallbackReference(); - if (callback != null && paths != null) { - callback.onPathsListed(paths); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - checkCallbackReference(); - } - - private OnPathsListedCallback checkCallbackReference() { - OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get(); - if (callback == null) { - cancel(false); - } - return callback; - } - - public interface OnPathsListedCallback { - - void onPathsListed(@NonNull String[] paths); - } - - public static class LoadingInfo { - - public final File file; - - final FileFilter fileFilter; - - public LoadingInfo(File file, FileFilter fileFilter) { - this.file = file; - this.fileFilter = fileFilter; - } - } - } - - private static class AsyncFileLoader extends WrappedAsyncTaskLoader> { - - private WeakReference fragmentWeakReference; - - AsyncFileLoader(FoldersFragment foldersFragment) { - super(foldersFragment.requireActivity()); - fragmentWeakReference = new WeakReference<>(foldersFragment); - } - - @Override - public List loadInBackground() { - FoldersFragment foldersFragment = fragmentWeakReference.get(); - File directory = null; - if (foldersFragment != null) { - BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb(); - if (crumb != null) { - directory = crumb.getFile(); + private void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { + if (crumb == null) { + return; } - } - if (directory != null) { - List files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER); - Collections.sort(files, foldersFragment.getFileComparator()); + saveScrollPosition(); + breadCrumbs.setActiveOrAdd(crumb, false); + if (addToHistory) { + breadCrumbs.addHistory(crumb); + } + LoaderManager.getInstance(this).restartLoader(LOADER_ID, null, this); + } + + private void setUpAdapter() { + adapter = + new SongFileAdapter(getMainActivity(), new LinkedList<>(), R.layout.item_list, this, this); + adapter.registerAdapterDataObserver( + new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + checkForPadding(); + } + }); + recyclerView.setAdapter(adapter); + checkIsEmpty(); + } + + private void setUpAppbarColor() { + breadCrumbs.setActivatedContentColor( + ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorPrimary)); + breadCrumbs.setDeactivatedContentColor( + ATHUtil.INSTANCE.resolveColor(requireContext(), android.R.attr.textColorSecondary)); + } + + private void setUpBreadCrumbs() { + breadCrumbs.setCallback(this); + } + + private void setUpRecyclerView() { + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + FastScroller fastScroller = ThemedFastScroller.INSTANCE.create(recyclerView); + recyclerView.setOnApplyWindowInsetsListener( + new ScrollingViewOnApplyWindowInsetsListener(recyclerView, fastScroller)); + } + + private ArrayList toList(File file) { + ArrayList files = new ArrayList<>(1); + files.add(file); return files; - } else { - return new LinkedList<>(); - } - } - } - - private static class ListSongsAsyncTask - extends ListingFilesDialogAsyncTask> { - - private final Object extra; - private WeakReference callbackWeakReference; - private WeakReference contextWeakReference; - - ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { - super(context); - this.extra = extra; - contextWeakReference = new WeakReference<>(context); - callbackWeakReference = new WeakReference<>(callback); } - @Override - protected List doInBackground(LoadingInfo... params) { - try { - LoadingInfo info = params[0]; - List files = FileUtil.listFilesDeep(info.files, info.fileFilter); + private void updateAdapter(@NonNull List files) { + adapter.swapDataSet(files); + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null && recyclerView != null) { + ((LinearLayoutManager) recyclerView.getLayoutManager()) + .scrollToPositionWithOffset(crumb.getScrollPosition(), 0); + } + } - if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) { - return null; + public static class ListPathsAsyncTask + extends ListingFilesDialogAsyncTask { + + private WeakReference onPathsListedCallbackWeakReference; + + public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { + super(context); + onPathsListedCallbackWeakReference = new WeakReference<>(callback); } - Collections.sort(files, info.fileComparator); + @Override + protected String[] doInBackground(LoadingInfo... params) { + try { + if (isCancelled() || checkCallbackReference() == null) { + return null; + } - Context context = checkContextReference(); - if (isCancelled() || context == null || checkCallbackReference() == null) { - return null; + LoadingInfo info = params[0]; + + final String[] paths; + + if (info.file.isDirectory()) { + List files = FileUtil.listFilesDeep(info.file, info.fileFilter); + + if (isCancelled() || checkCallbackReference() == null) { + return null; + } + + paths = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + File f = files.get(i); + paths[i] = FileUtil.safeGetCanonicalPath(f); + + if (isCancelled() || checkCallbackReference() == null) { + return null; + } + } + } else { + paths = new String[1]; + paths[0] = info.file.getPath(); + } + + return paths; + } catch (Exception e) { + e.printStackTrace(); + cancel(false); + return null; + } } - return FileUtil.matchFilesWithMediaStore(context, files); - } catch (Exception e) { - e.printStackTrace(); - cancel(false); - return null; - } + @Override + protected void onPostExecute(String[] paths) { + super.onPostExecute(paths); + OnPathsListedCallback callback = checkCallbackReference(); + if (callback != null && paths != null) { + callback.onPathsListed(paths); + } + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + checkCallbackReference(); + } + + private OnPathsListedCallback checkCallbackReference() { + OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; + } + + public interface OnPathsListedCallback { + + void onPathsListed(@NonNull String[] paths); + } + + public static class LoadingInfo { + + public final File file; + + final FileFilter fileFilter; + + public LoadingInfo(File file, FileFilter fileFilter) { + this.file = file; + this.fileFilter = fileFilter; + } + } } - @Override - protected void onPostExecute(List songs) { - super.onPostExecute(songs); - OnSongsListedCallback callback = checkCallbackReference(); - if (songs != null && callback != null) { - callback.onSongsListed(songs, extra); - } + private static class AsyncFileLoader extends WrappedAsyncTaskLoader> { + + private WeakReference fragmentWeakReference; + + AsyncFileLoader(FoldersFragment foldersFragment) { + super(foldersFragment.requireActivity()); + fragmentWeakReference = new WeakReference<>(foldersFragment); + } + + @Override + public List loadInBackground() { + FoldersFragment foldersFragment = fragmentWeakReference.get(); + File directory = null; + if (foldersFragment != null) { + BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb(); + if (crumb != null) { + directory = crumb.getFile(); + } + } + if (directory != null) { + List files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER); + Collections.sort(files, foldersFragment.getFileComparator()); + return files; + } else { + return new LinkedList<>(); + } + } } - @Override - protected void onPreExecute() { - super.onPreExecute(); - checkCallbackReference(); - checkContextReference(); + private static class ListSongsAsyncTask + extends ListingFilesDialogAsyncTask> { + + private final Object extra; + private WeakReference callbackWeakReference; + private WeakReference contextWeakReference; + + ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { + super(context); + this.extra = extra; + contextWeakReference = new WeakReference<>(context); + callbackWeakReference = new WeakReference<>(callback); + } + + @Override + protected List doInBackground(LoadingInfo... params) { + try { + LoadingInfo info = params[0]; + List files = FileUtil.listFilesDeep(info.files, info.fileFilter); + + if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) { + return null; + } + + Collections.sort(files, info.fileComparator); + + Context context = checkContextReference(); + if (isCancelled() || context == null || checkCallbackReference() == null) { + return null; + } + + return FileUtil.matchFilesWithMediaStore(context, files); + } catch (Exception e) { + e.printStackTrace(); + cancel(false); + return null; + } + } + + @Override + protected void onPostExecute(List songs) { + super.onPostExecute(songs); + OnSongsListedCallback callback = checkCallbackReference(); + if (songs != null && callback != null) { + callback.onSongsListed(songs, extra); + } + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + checkCallbackReference(); + checkContextReference(); + } + + private OnSongsListedCallback checkCallbackReference() { + OnSongsListedCallback callback = callbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; + } + + private Context checkContextReference() { + Context context = contextWeakReference.get(); + if (context == null) { + cancel(false); + } + return context; + } + + public interface OnSongsListedCallback { + + void onSongsListed(@NonNull List songs, Object extra); + } + + static class LoadingInfo { + + final Comparator fileComparator; + + final FileFilter fileFilter; + + final List files; + + LoadingInfo( + @NonNull List files, + @NonNull FileFilter fileFilter, + @NonNull Comparator fileComparator) { + this.fileComparator = fileComparator; + this.fileFilter = fileFilter; + this.files = files; + } + } } - private OnSongsListedCallback checkCallbackReference() { - OnSongsListedCallback callback = callbackWeakReference.get(); - if (callback == null) { - cancel(false); - } - return callback; + private abstract static class ListingFilesDialogAsyncTask + extends DialogAsyncTask { + + ListingFilesDialogAsyncTask(Context context) { + super(context); + } + + public ListingFilesDialogAsyncTask(Context context, int showDelay) { + super(context, showDelay); + } + + @Override + protected Dialog createDialog(@NonNull Context context) { + return new MaterialAlertDialogBuilder(context) + .setTitle(R.string.listing_files) + .setCancelable(false) + .setView(R.layout.loading) + .setOnCancelListener(dialog -> cancel(false)) + .setOnDismissListener(dialog -> cancel(false)) + .create(); + } } - - private Context checkContextReference() { - Context context = contextWeakReference.get(); - if (context == null) { - cancel(false); - } - return context; - } - - public interface OnSongsListedCallback { - - void onSongsListed(@NonNull List songs, Object extra); - } - - static class LoadingInfo { - - final Comparator fileComparator; - - final FileFilter fileFilter; - - final List files; - - LoadingInfo( - @NonNull List files, - @NonNull FileFilter fileFilter, - @NonNull Comparator fileComparator) { - this.fileComparator = fileComparator; - this.fileFilter = fileFilter; - this.files = files; - } - } - } - - private abstract static class ListingFilesDialogAsyncTask - extends DialogAsyncTask { - - ListingFilesDialogAsyncTask(Context context) { - super(context); - } - - public ListingFilesDialogAsyncTask(Context context, int showDelay) { - super(context, showDelay); - } - - @Override - protected Dialog createDialog(@NonNull Context context) { - return new MaterialAlertDialogBuilder(context) - .setTitle(R.string.listing_files) - .setCancelable(false) - .setView(R.layout.loading) - .setOnCancelListener(dialog -> cancel(false)) - .setOnDismissListener(dialog -> cancel(false)) - .create(); - } - } } diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt index 3868665c..520d0836 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/home/HomeFragment.kt @@ -52,6 +52,7 @@ class HomeFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + println("AbsMainActivityFragment") libraryViewModel.setPanelState(NowPlayingPanelState.COLLAPSED_WITH) mainActivity.setSupportActionBar(toolbar) mainActivity.supportActionBar?.title = null diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt index dc3e874c..35d4f292 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsFragment.kt @@ -5,10 +5,12 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import androidx.core.view.ViewCompat import androidx.core.view.isVisible import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.song.PlaylistSongAdapter import code.name.monkey.retromusic.db.PlaylistWithSongs @@ -18,6 +20,7 @@ import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.state.NowPlayingPanelState +import com.google.android.material.transition.MaterialContainerTransform import kotlinx.android.synthetic.main.fragment_playlist_detail.* import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -31,18 +34,29 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli private lateinit var playlist: PlaylistWithSongs private lateinit var playlistSongAdapter: PlaylistSongAdapter - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + private fun setUpTransitions() { + val transform = MaterialContainerTransform() + transform.setAllContainerColors(ATHUtil.resolveColor(requireContext(), R.attr.colorSurface)) + sharedElementEnterTransition = transform + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setUpTransitions() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) libraryViewModel.setPanelState(NowPlayingPanelState.COLLAPSED_WITHOUT) mainActivity.addMusicServiceEventListener(viewModel) mainActivity.setSupportActionBar(toolbar) + ViewCompat.setTransitionName(container, "playlist") playlist = arguments.extraPlaylist toolbar.title = playlist.playlistEntity.playlistName setUpRecyclerView() - viewModel.getSongs().observe(viewLifecycleOwner, { songs(it.toSongs()) }) diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt index bb97538d..3f6f874e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistDetailsViewModel.kt @@ -33,26 +33,7 @@ class PlaylistDetailsViewModel( fun getSongs(): LiveData> = realRepository.playlistSongs(playlist.playlistEntity.playListId) - override fun onMediaStoreChanged() { - /*if (playlist !is AbsCustomPlaylist) { - // Playlist deleted - if (!PlaylistsUtil.doesPlaylistExist(App.getContext(), playlist.id)) { - //TODO Finish the page - return - } - // Playlist renamed - val playlistName = - PlaylistsUtil.getNameForPlaylist(App.getContext(), playlist.id.toLong()) - if (playlistName != playlist.name) { - viewModelScope.launch { - playlist = realRepository.playlist(playlist.id) - _playlist.postValue(playlist) - } - } - } - loadPlaylistSongs(playlist)*/ - } - + override fun onMediaStoreChanged() {} override fun onServiceConnected() {} override fun onServiceDisconnected() {} override fun onQueueChanged() {} diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt index 27592935..cc5ef0fc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/playlists/PlaylistsFragment.kt @@ -18,19 +18,31 @@ import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.SubMenu import android.view.View -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.core.os.bundleOf +import androidx.core.view.MenuCompat +import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper +import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.playlist.PlaylistAdapter -import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewFragment +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.fragments.ReloadType +import code.name.monkey.retromusic.fragments.base.AbsRecyclerViewCustomGridSizeFragment +import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder +import code.name.monkey.retromusic.interfaces.IPlaylistClickListener +import code.name.monkey.retromusic.util.PreferenceUtil import kotlinx.android.synthetic.main.fragment_library.* -class PlaylistsFragment : AbsRecyclerViewFragment() { +class PlaylistsFragment : + AbsRecyclerViewCustomGridSizeFragment(), + IPlaylistClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - libraryViewModel.getPlaylists().observe(viewLifecycleOwner, Observer { + libraryViewModel.getPlaylists().observe(viewLifecycleOwner, { if (it.isNotEmpty()) adapter?.swapDataSet(it) else @@ -41,8 +53,8 @@ class PlaylistsFragment : AbsRecyclerViewFragment PlaylistSortOrder.PLAYLIST_A_Z + R.id.action_song_sort_order_desc -> PlaylistSortOrder.PLAYLIST_Z_A + R.id.action_playlist_sort_order -> PlaylistSortOrder.PLAYLIST_SONG_COUNT + R.id.action_playlist_sort_order_desc -> PlaylistSortOrder.PLAYLIST_SONG_COUNT_DESC + else -> PreferenceUtil.playlistSortOrder + } + if (sortOrder != PreferenceUtil.playlistSortOrder) { + item.isChecked = true + setAndSaveSortOrder(sortOrder) + return true + } + return false + } + + private fun createId(menu: SubMenu, id: Int, title: Int, checked: Boolean) { + menu.add(0, id, 0, title).isChecked = checked + } + + + override fun setGridSize(gridSize: Int) { + TODO("Not yet implemented") + } + + override fun setSortOrder(sortOrder: String) { + libraryViewModel.forceReload(ReloadType.Playlists) + } + + override fun loadSortOrder(): String { + return PreferenceUtil.playlistSortOrder + } + + override fun saveSortOrder(sortOrder: String) { + PreferenceUtil.playlistSortOrder = sortOrder + } + + override fun loadGridSize(): Int { + return 1 + } + + override fun saveGridSize(gridColumns: Int) { + //Add grid save + } + + override fun loadGridSizeLand(): Int { + return 2 + } + + override fun saveGridSizeLand(gridColumns: Int) { + //Add land grid save + } + + override fun loadLayoutRes(): Int { + return R.layout.item_list + } + + override fun saveLayoutRes(layoutRes: Int) { + //Save layout + } + + override fun onPlaylistClick(playlistWithSongs: PlaylistWithSongs, view: View) { + findNavController().navigate( + R.id.playlistDetailsFragment, + bundleOf(EXTRA_PLAYLIST to playlistWithSongs), + null, + FragmentNavigatorExtras(view to "playlist") + ) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt index b02ded2a..22f92a09 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.kt @@ -184,4 +184,25 @@ class SortOrder { const val ALBUM_Z_A = "$GENRE_A_Z DESC" } } + + /** + * Playlist sort order entries. + */ + interface PlaylistSortOrder { + + companion object { + + /* Playlist sort order A-Z */ + const val PLAYLIST_A_Z = MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER + + /* Playlist sort order Z-A */ + const val PLAYLIST_Z_A = "$PLAYLIST_A_Z DESC" + + /* Playlist sort order number of songs */ + const val PLAYLIST_SONG_COUNT = "playlist_song_count" + + /* Playlist sort order number of songs */ + const val PLAYLIST_SONG_COUNT_DESC = "$PLAYLIST_SONG_COUNT DESC" + } + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/IPlaylistClickListener.kt b/app/src/main/java/code/name/monkey/retromusic/interfaces/IPlaylistClickListener.kt new file mode 100644 index 00000000..3b0eb141 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/IPlaylistClickListener.kt @@ -0,0 +1,8 @@ +package code.name.monkey.retromusic.interfaces + +import android.view.View +import code.name.monkey.retromusic.db.PlaylistWithSongs + +interface IPlaylistClickListener { + fun onPlaylistClick(playlistWithSongs: PlaylistWithSongs, view: View) +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt index 94819043..ca7c822a 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/GenreRepository.kt @@ -95,13 +95,17 @@ class RealGenreRepository( } private fun makeGenreSongCursor(genreId: Long): Cursor? { - return contentResolver.query( - Genres.Members.getContentUri("external", genreId), - baseProjection, - IS_MUSIC, - null, - PreferenceUtil.songSortOrder - ) + return try { + contentResolver.query( + Genres.Members.getContentUri("external", genreId), + baseProjection, + IS_MUSIC, + null, + PreferenceUtil.songSortOrder + ) + } catch (e: SecurityException) { + return null + } } private fun getGenresFromCursor(cursor: Cursor?): ArrayList { @@ -143,17 +147,18 @@ class RealGenreRepository( return genres } - private fun makeGenreCursor(): Cursor? { val projection = arrayOf(Genres._ID, Genres.NAME) - return contentResolver.query( - Genres.EXTERNAL_CONTENT_URI, - projection, - null, - null, - PreferenceUtil.genreSortOrder - ) + return try { + contentResolver.query( + Genres.EXTERNAL_CONTENT_URI, + projection, + null, + null, + PreferenceUtil.genreSortOrder + ) + } catch (e: SecurityException) { + return null + } } - - } diff --git a/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt b/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt index ca8a9833..e57c5c57 100644 --- a/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt +++ b/app/src/main/java/code/name/monkey/retromusic/repository/RoomRepository.kt @@ -2,8 +2,24 @@ package code.name.monkey.retromusic.repository import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData -import code.name.monkey.retromusic.db.* +import code.name.monkey.retromusic.db.BlackListStoreDao +import code.name.monkey.retromusic.db.BlackListStoreEntity +import code.name.monkey.retromusic.db.HistoryDao +import code.name.monkey.retromusic.db.HistoryEntity +import code.name.monkey.retromusic.db.LyricsDao +import code.name.monkey.retromusic.db.PlayCountDao +import code.name.monkey.retromusic.db.PlayCountEntity +import code.name.monkey.retromusic.db.PlaylistDao +import code.name.monkey.retromusic.db.PlaylistEntity +import code.name.monkey.retromusic.db.PlaylistWithSongs +import code.name.monkey.retromusic.db.SongEntity +import code.name.monkey.retromusic.db.toHistoryEntity +import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_A_Z +import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT +import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_SONG_COUNT_DESC +import code.name.monkey.retromusic.helper.SortOrder.PlaylistSortOrder.Companion.PLAYLIST_Z_A import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.util.PreferenceUtil interface RoomRepository { @@ -61,7 +77,22 @@ class RealRoomRepository( @WorkerThread override suspend fun playlistWithSongs(): List = - playlistDao.playlistsWithSongs() + when (PreferenceUtil.playlistSortOrder) { + PLAYLIST_A_Z -> + playlistDao.playlistsWithSongs().sortedBy { + it.playlistEntity.playlistName + } + PLAYLIST_Z_A -> playlistDao.playlistsWithSongs() + .sortedByDescending { + it.playlistEntity.playlistName + } + PLAYLIST_SONG_COUNT -> playlistDao.playlistsWithSongs().sortedBy { it.songs.size } + PLAYLIST_SONG_COUNT_DESC -> playlistDao.playlistsWithSongs() + .sortedByDescending { it.songs.size } + else -> playlistDao.playlistsWithSongs().sortedBy { + it.playlistEntity.playlistName + } + } @WorkerThread override suspend fun insertSongs(songs: List) { diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java index f9abe7d6..b7d39972 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -14,13 +14,6 @@ package code.name.monkey.retromusic.service; -import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; -import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; -import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.GAP_LESS_PLAYBACK; -import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; - import android.app.PendingIntent; import android.app.Service; import android.appwidget.AppWidgetManager; @@ -54,9 +47,21 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; + +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Random; + import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.activities.LockScreenActivity; import code.name.monkey.retromusic.appwidgets.AppWidgetBig; @@ -79,1368 +84,1359 @@ import code.name.monkey.retromusic.service.playback.Playback; import code.name.monkey.retromusic.util.MusicUtil; import code.name.monkey.retromusic.util.PreferenceUtil; import code.name.monkey.retromusic.util.RetroUtil; -import com.bumptech.glide.BitmapRequestBuilder; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.SimpleTarget; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Random; -/** @author Karim Abou Zeid (kabouzeid), Andrew Neal */ +import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; +import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; +import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; +import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; +import static code.name.monkey.retromusic.ConstantsKt.GAP_LESS_PLAYBACK; +import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; + +/** + * @author Karim Abou Zeid (kabouzeid), Andrew Neal + */ public class MusicService extends Service - implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { + implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { - public static final String TAG = MusicService.class.getSimpleName(); - public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; - public static final String MUSIC_PACKAGE_NAME = "com.android.music"; - public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause"; - public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play"; - public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist"; - public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause"; - public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop"; - public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip"; - public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind"; - public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice"; - public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice"; - public static final String INTENT_EXTRA_PLAYLIST = - RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"; - public static final String INTENT_EXTRA_SHUFFLE_MODE = - RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode"; - public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate"; - public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"; - // Do not change these three strings as it will break support with other apps (e.g. last.fm - // scrobbling) - public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged"; - public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged"; - public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged"; - public static final String FAVORITE_STATE_CHANGED = - RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged"; - public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged"; - public static final String SHUFFLE_MODE_CHANGED = - RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged"; - public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged"; - public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat"; - public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle"; - public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite"; - public static final String SAVED_POSITION = "POSITION"; - public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; - public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; - public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; - public static final int RELEASE_WAKELOCK = 0; - public static final int TRACK_ENDED = 1; - public static final int TRACK_WENT_TO_NEXT = 2; - public static final int PLAY_SONG = 3; - public static final int PREPARE_NEXT = 4; - public static final int SET_POSITION = 5; - public static final int FOCUS_CHANGE = 6; - public static final int DUCK = 7; - public static final int UNDUCK = 8; - public static final int RESTORE_QUEUES = 9; - public static final int SHUFFLE_MODE_NONE = 0; - public static final int SHUFFLE_MODE_SHUFFLE = 1; - public static final int REPEAT_MODE_NONE = 0; - public static final int REPEAT_MODE_ALL = 1; - public static final int REPEAT_MODE_THIS = 2; - public static final int SAVE_QUEUES = 0; - private static final long MEDIA_SESSION_ACTIONS = - PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE - | PlaybackStateCompat.ACTION_PLAY_PAUSE - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_STOP - | PlaybackStateCompat.ACTION_SEEK_TO; - private final IBinder musicBind = new MusicBinder(); - public int nextPosition = -1; + public static final String TAG = MusicService.class.getSimpleName(); + public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; + public static final String MUSIC_PACKAGE_NAME = "com.android.music"; + public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause"; + public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play"; + public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist"; + public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause"; + public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop"; + public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip"; + public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind"; + public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice"; + public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice"; + public static final String INTENT_EXTRA_PLAYLIST = + RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"; + public static final String INTENT_EXTRA_SHUFFLE_MODE = + RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode"; + public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate"; + public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"; + // Do not change these three strings as it will break support with other apps (e.g. last.fm + // scrobbling) + public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged"; + public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged"; + public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged"; + public static final String FAVORITE_STATE_CHANGED = + RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged"; + public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged"; + public static final String SHUFFLE_MODE_CHANGED = + RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged"; + public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged"; + public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat"; + public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle"; + public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite"; + public static final String SAVED_POSITION = "POSITION"; + public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; + public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; + public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; + public static final int RELEASE_WAKELOCK = 0; + public static final int TRACK_ENDED = 1; + public static final int TRACK_WENT_TO_NEXT = 2; + public static final int PLAY_SONG = 3; + public static final int PREPARE_NEXT = 4; + public static final int SET_POSITION = 5; + public static final int FOCUS_CHANGE = 6; + public static final int DUCK = 7; + public static final int UNDUCK = 8; + public static final int RESTORE_QUEUES = 9; + public static final int SHUFFLE_MODE_NONE = 0; + public static final int SHUFFLE_MODE_SHUFFLE = 1; + public static final int REPEAT_MODE_NONE = 0; + public static final int REPEAT_MODE_ALL = 1; + public static final int REPEAT_MODE_THIS = 2; + public static final int SAVE_QUEUES = 0; + private static final long MEDIA_SESSION_ACTIONS = + PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_STOP + | PlaybackStateCompat.ACTION_SEEK_TO; + private final IBinder musicBind = new MusicBinder(); + public int nextPosition = -1; - public boolean pendingQuit = false; + public boolean pendingQuit = false; - @Nullable public Playback playback; + @Nullable + public Playback playback; - public int position = -1; + public int position = -1; - private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); + private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); - private AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); + private AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); - private AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); + private AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); - private AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); + private AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); - private AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); + private AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); - private final BroadcastReceiver widgetIntentReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); - final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); - if (command != null) { - switch (command) { - case AppWidgetClassic.NAME: - { - appWidgetClassic.performUpdate(MusicService.this, ids); - break; + private final BroadcastReceiver widgetIntentReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + if (command != null) { + switch (command) { + case AppWidgetClassic.NAME: { + appWidgetClassic.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetSmall.NAME: { + appWidgetSmall.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetBig.NAME: { + appWidgetBig.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetCard.NAME: { + appWidgetCard.performUpdate(MusicService.this, ids); + break; + } + case AppWidgetText.NAME: { + appWidgetText.performUpdate(MusicService.this, ids); + break; + } + } + } } - case AppWidgetSmall.NAME: - { - appWidgetSmall.performUpdate(MusicService.this, ids); - break; + }; + private AudioManager audioManager; + private IntentFilter becomingNoisyReceiverIntentFilter = + new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + private boolean becomingNoisyReceiverRegistered; + private IntentFilter bluetoothConnectedIntentFilter = + new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); + private boolean bluetoothConnectedRegistered = false; + private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + private boolean headsetReceiverRegistered = false; + private MediaSessionCompat mediaSession; + private ContentObserver mediaStoreObserver; + private HandlerThread musicPlayerHandlerThread; + private boolean notHandledMetaChangedForCurrentTrack; + private List originalPlayingQueue = new ArrayList<>(); + private List playingQueue = new ArrayList<>(); + private boolean pausedByTransientLossOfFocus; + + private final BroadcastReceiver becomingNoisyReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, @NonNull Intent intent) { + if (intent.getAction() != null + && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + pause(); + } } - case AppWidgetBig.NAME: - { - appWidgetBig.performUpdate(MusicService.this, ids); - break; + }; + + private PlaybackHandler playerHandler; + + private final AudioManager.OnAudioFocusChangeListener audioFocusListener = + new AudioManager.OnAudioFocusChangeListener() { + @Override + public void onAudioFocusChange(final int focusChange) { + playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); } - case AppWidgetCard.NAME: - { - appWidgetCard.performUpdate(MusicService.this, ids); - break; + }; + + private PlayingNotification playingNotification; + private final BroadcastReceiver updateFavoriteReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + updateNotification(); } - case AppWidgetText.NAME: - { - appWidgetText.performUpdate(MusicService.this, ids); - break; + }; + private final BroadcastReceiver lockScreenReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (PreferenceUtil.INSTANCE.isLockScreen() && isPlaying()) { + Intent lockIntent = new Intent(context, LockScreenActivity.class); + lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(lockIntent); + } } - } - } - } - }; - private AudioManager audioManager; - private IntentFilter becomingNoisyReceiverIntentFilter = - new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - private boolean becomingNoisyReceiverRegistered; - private IntentFilter bluetoothConnectedIntentFilter = - new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); - private boolean bluetoothConnectedRegistered = false; - private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - private boolean headsetReceiverRegistered = false; - private MediaSessionCompat mediaSession; - private ContentObserver mediaStoreObserver; - private HandlerThread musicPlayerHandlerThread; - private boolean notHandledMetaChangedForCurrentTrack; - private List originalPlayingQueue = new ArrayList<>(); - private List playingQueue = new ArrayList<>(); - private boolean pausedByTransientLossOfFocus; - - private final BroadcastReceiver becomingNoisyReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, @NonNull Intent intent) { - if (intent.getAction() != null - && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { - pause(); - } - } - }; - - private PlaybackHandler playerHandler; - - private final AudioManager.OnAudioFocusChangeListener audioFocusListener = - new AudioManager.OnAudioFocusChangeListener() { - @Override - public void onAudioFocusChange(final int focusChange) { - playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); - } - }; - - private PlayingNotification playingNotification; - private final BroadcastReceiver updateFavoriteReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - updateNotification(); - } - }; - private final BroadcastReceiver lockScreenReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (PreferenceUtil.INSTANCE.isLockScreen() && isPlaying()) { - Intent lockIntent = new Intent(context, LockScreenActivity.class); - lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(lockIntent); - } - } - }; - private QueueSaveHandler queueSaveHandler; - private HandlerThread queueSaveHandlerThread; - private boolean queuesRestored; - private int repeatMode; - private int shuffleMode; - private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); - private final BroadcastReceiver bluetoothReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) - && PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { - if (VERSION.SDK_INT >= VERSION_CODES.M) { - if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { - play(); + }; + private QueueSaveHandler queueSaveHandler; + private HandlerThread queueSaveHandlerThread; + private boolean queuesRestored; + private int repeatMode; + private int shuffleMode; + private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); + private final BroadcastReceiver bluetoothReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + String action = intent.getAction(); + if (action != null) { + if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) + && PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { + if (VERSION.SDK_INT >= VERSION_CODES.M) { + if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { + play(); + } + } else { + if (getAudioManager().isBluetoothA2dpOn()) { + play(); + } + } + } + } } - } else { - if (getAudioManager().isBluetoothA2dpOn()) { - play(); + }; + private PhoneStateListener phoneStateListener = + new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + // Not in call: Play music + play(); + break; + case TelephonyManager.CALL_STATE_RINGING: + case TelephonyManager.CALL_STATE_OFFHOOK: + // A call is dialing, active or on hold + pause(); + break; + default: + } + super.onCallStateChanged(state, incomingNumber); } - } - } - } + }; + private BroadcastReceiver headsetReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null) { + if (Intent.ACTION_HEADSET_PLUG.equals(action)) { + int state = intent.getIntExtra("state", -1); + switch (state) { + case 0: + pause(); + break; + case 1: + play(); + break; + } + } + } + } + }; + private ThrottledSeekHandler throttledSeekHandler; + private Handler uiThreadHandler; + private PowerManager.WakeLock wakeLock; + + private static Bitmap copy(Bitmap bitmap) { + Bitmap.Config config = bitmap.getConfig(); + if (config == null) { + config = Bitmap.Config.RGB_565; } - }; - private PhoneStateListener phoneStateListener = - new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String incomingNumber) { - switch (state) { - case TelephonyManager.CALL_STATE_IDLE: - // Not in call: Play music - play(); - break; - case TelephonyManager.CALL_STATE_RINGING: - case TelephonyManager.CALL_STATE_OFFHOOK: - // A call is dialing, active or on hold - pause(); - break; - default: - } - super.onCallStateChanged(state, incomingNumber); + try { + return bitmap.copy(config, false); + } catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; } - }; - private BroadcastReceiver headsetReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (Intent.ACTION_HEADSET_PLUG.equals(action)) { - int state = intent.getIntExtra("state", -1); - switch (state) { - case 0: - pause(); - break; - case 1: - play(); - break; - } - } - } + } + + private static String getTrackUri(@NonNull Song song) { + return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString(); + } + + @Override + public void onCreate() { + super.onCreate(); + final TelephonyManager telephonyManager = + (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + if (telephonyManager != null) { + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); } - }; - private ThrottledSeekHandler throttledSeekHandler; - private Handler uiThreadHandler; - private PowerManager.WakeLock wakeLock; - private static Bitmap copy(Bitmap bitmap) { - Bitmap.Config config = bitmap.getConfig(); - if (config == null) { - config = Bitmap.Config.RGB_565; - } - try { - return bitmap.copy(config, false); - } catch (OutOfMemoryError e) { - e.printStackTrace(); - return null; - } - } - - private static String getTrackUri(@NonNull Song song) { - return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString(); - } - - @Override - public void onCreate() { - super.onCreate(); - final TelephonyManager telephonyManager = - (TelephonyManager) getSystemService(TELEPHONY_SERVICE); - if (telephonyManager != null) { - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); - } - - final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - if (powerManager != null) { - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); - } - wakeLock.setReferenceCounted(false); - - musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); - musicPlayerHandlerThread.start(); - playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); - - playback = new MultiPlayer(this); - playback.setCallbacks(this); - - setupMediaSession(); - - // queue saving needs to run on a separate thread so that it doesn't block the playback handler - // events - queueSaveHandlerThread = - new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); - queueSaveHandlerThread.start(); - queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); - - uiThreadHandler = new Handler(); - - registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); - registerReceiver(updateFavoriteReceiver, new IntentFilter(FAVORITE_STATE_CHANGED)); - registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - - initNotification(); - - mediaStoreObserver = new MediaStoreObserver(this, playerHandler); - throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - - PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); - - restoreState(); - - sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); - - registerHeadsetEvents(); - registerBluetoothConnected(); - } - - @Override - public void onDestroy() { - unregisterReceiver(widgetIntentReceiver); - unregisterReceiver(updateFavoriteReceiver); - unregisterReceiver(lockScreenReceiver); - if (becomingNoisyReceiverRegistered) { - unregisterReceiver(becomingNoisyReceiver); - becomingNoisyReceiverRegistered = false; - } - if (headsetReceiverRegistered) { - unregisterReceiver(headsetReceiver); - headsetReceiverRegistered = false; - } - if (bluetoothConnectedRegistered) { - unregisterReceiver(bluetoothReceiver); - bluetoothConnectedRegistered = false; - } - mediaSession.setActive(false); - quit(); - releaseResources(); - getContentResolver().unregisterContentObserver(mediaStoreObserver); - PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this); - wakeLock.release(); - - sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED")); - } - - public void acquireWakeLock(long milli) { - wakeLock.acquire(milli); - } - - public void addSong(int position, Song song) { - playingQueue.add(position, song); - originalPlayingQueue.add(position, song); - notifyChange(QUEUE_CHANGED); - } - - public void addSong(Song song) { - playingQueue.add(song); - originalPlayingQueue.add(song); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(int position, List songs) { - playingQueue.addAll(position, songs); - originalPlayingQueue.addAll(position, songs); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(List songs) { - playingQueue.addAll(songs); - originalPlayingQueue.addAll(songs); - notifyChange(QUEUE_CHANGED); - } - - public void back(boolean force) { - if (getSongProgressMillis() > 2000) { - seek(0); - } else { - playPreviousSong(force); - } - } - - public void clearQueue() { - playingQueue.clear(); - originalPlayingQueue.clear(); - - setPosition(-1); - notifyChange(QUEUE_CHANGED); - } - - public void cycleRepeatMode() { - switch (getRepeatMode()) { - case REPEAT_MODE_NONE: - setRepeatMode(REPEAT_MODE_ALL); - break; - case REPEAT_MODE_ALL: - setRepeatMode(REPEAT_MODE_THIS); - break; - default: - setRepeatMode(REPEAT_MODE_NONE); - break; - } - } - - public int getAudioSessionId() { - if (playback != null) { - return playback.getAudioSessionId(); - } - return -1; - } - - @NonNull - public Song getCurrentSong() { - return getSongAt(getPosition()); - } - - @NonNull - public MediaSessionCompat getMediaSession() { - return mediaSession; - } - - public int getNextPosition(boolean force) { - int position = getPosition() + 1; - switch (getRepeatMode()) { - case REPEAT_MODE_ALL: - if (isLastTrack()) { - position = 0; + final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); } - break; - case REPEAT_MODE_THIS: - if (force) { - if (isLastTrack()) { - position = 0; - } - } else { - position -= 1; - } - break; - default: - case REPEAT_MODE_NONE: - if (isLastTrack()) { - position -= 1; - } - break; - } - return position; - } + wakeLock.setReferenceCounted(false); - @Nullable - public List getPlayingQueue() { - return playingQueue; - } + musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); + musicPlayerHandlerThread.start(); + playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); - public int getPosition() { - return position; - } + playback = new MultiPlayer(this); + playback.setCallbacks(this); - public void setPosition(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(SET_POSITION); - playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); - } + setupMediaSession(); - public int getPreviousPosition(boolean force) { - int newPosition = getPosition() - 1; - switch (repeatMode) { - case REPEAT_MODE_ALL: - if (newPosition < 0) { - if (getPlayingQueue() != null) { - newPosition = getPlayingQueue().size() - 1; - } - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (newPosition < 0) { - if (getPlayingQueue() != null) { - newPosition = getPlayingQueue().size() - 1; - } - } - } else { - newPosition = getPosition(); - } - break; - default: - case REPEAT_MODE_NONE: - if (newPosition < 0) { - newPosition = 0; - } - break; - } - return newPosition; - } + // queue saving needs to run on a separate thread so that it doesn't block the playback handler + // events + queueSaveHandlerThread = + new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); + queueSaveHandlerThread.start(); + queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); - public long getQueueDurationMillis(int position) { - long duration = 0; - for (int i = position + 1; i < playingQueue.size(); i++) { - duration += playingQueue.get(i).getDuration(); - } - return duration; - } + uiThreadHandler = new Handler(); - public int getRepeatMode() { - return repeatMode; - } + registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); + registerReceiver(updateFavoriteReceiver, new IntentFilter(FAVORITE_STATE_CHANGED)); + registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - public void setRepeatMode(final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_NONE: - case REPEAT_MODE_ALL: - case REPEAT_MODE_THIS: - this.repeatMode = repeatMode; - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_REPEAT_MODE, repeatMode) - .apply(); - prepareNext(); - handleAndSendChangeInternal(REPEAT_MODE_CHANGED); - break; - } - } - - public int getShuffleMode() { - return shuffleMode; - } - - public void setShuffleMode(final int shuffleMode) { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_SHUFFLE_MODE, shuffleMode) - .apply(); - switch (shuffleMode) { - case SHUFFLE_MODE_SHUFFLE: - this.shuffleMode = shuffleMode; - if (this.getPlayingQueue() != null) { - ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition()); - } - position = 0; - break; - case SHUFFLE_MODE_NONE: - this.shuffleMode = shuffleMode; - long currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); - playingQueue = new ArrayList<>(originalPlayingQueue); - int newPosition = 0; - if (getPlayingQueue() != null) { - for (Song song : getPlayingQueue()) { - if (song.getId() == currentSongId) { - newPosition = getPlayingQueue().indexOf(song); - } - } - } - position = newPosition; - break; - } - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); - notifyChange(QUEUE_CHANGED); - } - - @NonNull - public Song getSongAt(int position) { - if (position >= 0 && getPlayingQueue() != null && position < getPlayingQueue().size()) { - return getPlayingQueue().get(position); - } else { - return Song.Companion.getEmptySong(); - } - } - - public int getSongDurationMillis() { - if (playback != null) { - return playback.duration(); - } - return -1; - } - - public int getSongProgressMillis() { - if (playback != null) { - return playback.position(); - } - return -1; - } - - public void handleAndSendChangeInternal(@NonNull final String what) { - handleChangeInternal(what); - sendChangeInternal(what); - } - - public void initNotification() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - && !PreferenceUtil.INSTANCE.isClassicNotification()) { - playingNotification = new PlayingNotificationImpl(); - } else { - playingNotification = new PlayingNotificationOreo(); - } - playingNotification.init(this); - } - - public boolean isLastTrack() { - if (getPlayingQueue() != null) { - return getPosition() == getPlayingQueue().size() - 1; - } - return false; - } - - public boolean isPausedByTransientLossOfFocus() { - return pausedByTransientLossOfFocus; - } - - public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) { - this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus; - } - - public boolean isPlaying() { - return playback != null && playback.isPlaying(); - } - - public void moveSong(int from, int to) { - if (from == to) { - return; - } - final int currentPosition = getPosition(); - Song songToMove = playingQueue.remove(from); - playingQueue.add(to, songToMove); - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - Song tmpSong = originalPlayingQueue.remove(from); - originalPlayingQueue.add(to, tmpSong); - } - if (from > currentPosition && to <= currentPosition) { - position = currentPosition + 1; - } else if (from < currentPosition && to >= currentPosition) { - position = currentPosition - 1; - } else if (from == currentPosition) { - position = to; - } - notifyChange(QUEUE_CHANGED); - } - - public void notifyChange(@NonNull final String what) { - handleAndSendChangeInternal(what); - sendPublicIntent(what); - } - - @NonNull - @Override - public IBinder onBind(Intent intent) { - return musicBind; - } - - @Override - public void onSharedPreferenceChanged( - @NonNull SharedPreferences sharedPreferences, @NonNull String key) { - switch (key) { - case GAP_LESS_PLAYBACK: - if (sharedPreferences.getBoolean(key, false)) { - prepareNext(); - } else { - if (playback != null) { - playback.setNextDataSource(null); - } - } - break; - case ALBUM_ART_ON_LOCK_SCREEN: - case BLURRED_ALBUM_ART: - updateMediaSessionMetaData(); - break; - case COLORED_NOTIFICATION: - updateNotification(); - break; - case CLASSIC_NOTIFICATION: initNotification(); - updateNotification(); - break; - case TOGGLE_HEADSET: + + mediaStoreObserver = new MediaStoreObserver(this, playerHandler); + throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver() + .registerContentObserver( + MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + + PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); + + restoreState(); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); + registerHeadsetEvents(); - break; - } - } - - @Override - public int onStartCommand(@Nullable Intent intent, int flags, int startId) { - if (intent != null && intent.getAction() != null) { - restoreQueuesAndPositionIfNecessary(); - String action = intent.getAction(); - switch (action) { - case ACTION_TOGGLE_PAUSE: - if (isPlaying()) { - pause(); - } else { - play(); - } - break; - case ACTION_PAUSE: - pause(); - break; - case ACTION_PLAY: - play(); - break; - case ACTION_PLAY_PLAYLIST: - playFromPlaylist(intent); - break; - case ACTION_REWIND: - back(true); - break; - case ACTION_SKIP: - playNextSong(true); - break; - case ACTION_STOP: - case ACTION_QUIT: - pendingQuit = false; - quit(); - break; - case ACTION_PENDING_QUIT: - pendingQuit = true; - break; - case TOGGLE_FAVORITE: - MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong()); - break; - } + registerBluetoothConnected(); } - return START_NOT_STICKY; - } - - @Override - public void onTrackEnded() { - acquireWakeLock(30000); - playerHandler.sendEmptyMessage(TRACK_ENDED); - } - - @Override - public void onTrackWentToNext() { - playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); - } - - @Override - public boolean onUnbind(Intent intent) { - if (!isPlaying()) { - stopSelf(); - } - return true; - } - - public void openQueue( - @Nullable final List playingQueue, - final int startPosition, - final boolean startPlaying) { - if (playingQueue != null - && !playingQueue.isEmpty() - && startPosition >= 0 - && startPosition < playingQueue.size()) { - // it is important to copy the playing queue here first as we might add/remove songs later - originalPlayingQueue = new ArrayList<>(playingQueue); - this.playingQueue = new ArrayList<>(originalPlayingQueue); - - int position = startPosition; - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition); - position = 0; - } - if (startPlaying) { - playSongAt(position); - } else { - setPosition(position); - } - notifyChange(QUEUE_CHANGED); - } - } - - public boolean openTrackAndPrepareNextAt(int position) { - synchronized (this) { - this.position = position; - boolean prepared = openCurrent(); - if (prepared) { - prepareNextImpl(); - } - notifyChange(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; - return prepared; - } - } - - public void pause() { - pausedByTransientLossOfFocus = false; - if (playback != null && playback.isPlaying()) { - playback.pause(); - notifyChange(PLAY_STATE_CHANGED); - } - } - - public void play() { - synchronized (this) { - if (requestFocus()) { - if (playback != null && !playback.isPlaying()) { - if (!playback.isInitialized()) { - playSongAt(getPosition()); - } else { - playback.start(); - if (!becomingNoisyReceiverRegistered) { - registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); - becomingNoisyReceiverRegistered = true; - } - if (notHandledMetaChangedForCurrentTrack) { - handleChangeInternal(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; - } - notifyChange(PLAY_STATE_CHANGED); - - // fixes a bug where the volume would stay ducked because the - // AudioManager.AUDIOFOCUS_GAIN event is not sent - playerHandler.removeMessages(DUCK); - playerHandler.sendEmptyMessage(UNDUCK); - } + @Override + public void onDestroy() { + unregisterReceiver(widgetIntentReceiver); + unregisterReceiver(updateFavoriteReceiver); + unregisterReceiver(lockScreenReceiver); + if (becomingNoisyReceiverRegistered) { + unregisterReceiver(becomingNoisyReceiver); + becomingNoisyReceiverRegistered = false; } - } else { - Toast.makeText( - this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT) - .show(); - } + if (headsetReceiverRegistered) { + unregisterReceiver(headsetReceiver); + headsetReceiverRegistered = false; + } + if (bluetoothConnectedRegistered) { + unregisterReceiver(bluetoothReceiver); + bluetoothConnectedRegistered = false; + } + mediaSession.setActive(false); + quit(); + releaseResources(); + getContentResolver().unregisterContentObserver(mediaStoreObserver); + PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this); + wakeLock.release(); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED")); } - } - public void playNextSong(boolean force) { - playSongAt(getNextPosition(force)); - } - - public void playPreviousSong(boolean force) { - playSongAt(getPreviousPosition(force)); - } - - public void playSongAt(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(PLAY_SONG); - playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); - } - - public void playSongAtImpl(int position) { - if (openTrackAndPrepareNextAt(position)) { - play(); - } else { - Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT) - .show(); + public void acquireWakeLock(long milli) { + wakeLock.acquire(milli); } - } - public void playSongs(ArrayList songs, int shuffleMode) { - if (songs != null && !songs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - int startPosition = new Random().nextInt(songs.size()); - openQueue(songs, startPosition, false); - setShuffleMode(shuffleMode); - } else { - openQueue(songs, 0, false); - } - play(); - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + public void addSong(int position, Song song) { + playingQueue.add(position, song); + originalPlayingQueue.add(position, song); + notifyChange(QUEUE_CHANGED); } - } - public boolean prepareNextImpl() { - synchronized (this) { - try { - int nextPosition = getNextPosition(false); + public void addSong(Song song) { + playingQueue.add(song); + originalPlayingQueue.add(song); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(int position, List songs) { + playingQueue.addAll(position, songs); + originalPlayingQueue.addAll(position, songs); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(List songs) { + playingQueue.addAll(songs); + originalPlayingQueue.addAll(songs); + notifyChange(QUEUE_CHANGED); + } + + public void back(boolean force) { + if (getSongProgressMillis() > 2000) { + seek(0); + } else { + playPreviousSong(force); + } + } + + public void clearQueue() { + playingQueue.clear(); + originalPlayingQueue.clear(); + + setPosition(-1); + notifyChange(QUEUE_CHANGED); + } + + public void cycleRepeatMode() { + switch (getRepeatMode()) { + case REPEAT_MODE_NONE: + setRepeatMode(REPEAT_MODE_ALL); + break; + case REPEAT_MODE_ALL: + setRepeatMode(REPEAT_MODE_THIS); + break; + default: + setRepeatMode(REPEAT_MODE_NONE); + break; + } + } + + public int getAudioSessionId() { if (playback != null) { - playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition)))); + return playback.getAudioSessionId(); } - this.nextPosition = nextPosition; - return true; - } catch (Exception e) { - return false; - } - } - } - - public void quit() { - pause(); - playingNotification.stop(); - - closeAudioEffectSession(); - getAudioManager().abandonAudioFocus(audioFocusListener); - stopSelf(); - } - - public void releaseWakeLock() { - if (wakeLock.isHeld()) { - wakeLock.release(); - } - } - - public void removeSong(int position) { - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - playingQueue.remove(position); - originalPlayingQueue.remove(position); - } else { - originalPlayingQueue.remove(playingQueue.remove(position)); - } - - rePosition(position); - - notifyChange(QUEUE_CHANGED); - } - - public void removeSong(@NonNull Song song) { - for (int i = 0; i < playingQueue.size(); i++) { - if (playingQueue.get(i).getId() == song.getId()) { - playingQueue.remove(i); - rePosition(i); - } - } - for (int i = 0; i < originalPlayingQueue.size(); i++) { - if (originalPlayingQueue.get(i).getId() == song.getId()) { - originalPlayingQueue.remove(i); - } - } - notifyChange(QUEUE_CHANGED); - } - - public synchronized void restoreQueuesAndPositionIfNecessary() { - if (!queuesRestored && playingQueue.isEmpty()) { - List restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); - List restoredOriginalQueue = - MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); - int restoredPosition = - PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); - int restoredPositionInTrack = - PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); - - if (restoredQueue.size() > 0 - && restoredQueue.size() == restoredOriginalQueue.size() - && restoredPosition != -1) { - this.originalPlayingQueue = restoredOriginalQueue; - this.playingQueue = restoredQueue; - - position = restoredPosition; - openCurrent(); - prepareNext(); - - if (restoredPositionInTrack > 0) { - seek(restoredPositionInTrack); - } - - notHandledMetaChangedForCurrentTrack = true; - sendChangeInternal(META_CHANGED); - sendChangeInternal(QUEUE_CHANGED); - } - } - queuesRestored = true; - } - - public void runOnUiThread(Runnable runnable) { - uiThreadHandler.post(runnable); - } - - public void savePositionInTrack() { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()) - .apply(); - } - - public void saveQueuesImpl() { - MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); - } - - public void saveState() { - saveQueues(); - savePosition(); - savePositionInTrack(); - } - - public int seek(int millis) { - synchronized (this) { - try { - int newPosition = 0; - if (playback != null) { - newPosition = playback.seek(millis); - } - throttledSeekHandler.notifySeek(); - return newPosition; - } catch (Exception e) { return -1; - } } - } - - // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch - public void sendPublicIntent(@NonNull final String what) { - final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); - - final Song song = getCurrentSong(); - - if (song != null) { - intent.putExtra("id", song.getId()); - intent.putExtra("artist", song.getArtistName()); - intent.putExtra("album", song.getAlbumName()); - intent.putExtra("track", song.getTitle()); - intent.putExtra("duration", song.getDuration()); - intent.putExtra("position", (long) getSongProgressMillis()); - intent.putExtra("playing", isPlaying()); - intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); - sendStickyBroadcast(intent); - } - } - - public void toggleShuffle() { - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - setShuffleMode(SHUFFLE_MODE_SHUFFLE); - } else { - setShuffleMode(SHUFFLE_MODE_NONE); - } - } - - public void updateMediaSessionPlaybackState() { - PlaybackStateCompat.Builder stateBuilder = - new PlaybackStateCompat.Builder() - .setActions(MEDIA_SESSION_ACTIONS) - .setState( - isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, - getSongProgressMillis(), - 1); - - setCustomAction(stateBuilder); - - mediaSession.setPlaybackState(stateBuilder.build()); - } - - public void updateNotification() { - if (playingNotification != null && getCurrentSong().getId() != -1) { - playingNotification.update(); - } - } - - void updateMediaSessionMetaData() { - final Song song = getCurrentSong(); - - if (song.getId() == -1) { - mediaSession.setMetadata(null); - return; - } - - final MediaMetadataCompat.Builder metaData = - new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName()) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName()) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName()) - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle()) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration()) - .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) - .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear()) - .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null) - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); - - if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) { - final Point screenSize = RetroUtil.getScreenSize(MusicService.this); - final BitmapRequestBuilder request = - SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) - .checkIgnoreMediaStore(MusicService.this) - .asBitmap() - .build(); - if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { - request.transform(new BlurTransformation.Builder(MusicService.this).build()); - } - runOnUiThread( - new Runnable() { - @Override - public void run() { - request.into( - new SimpleTarget(screenSize.x, screenSize.y) { - @Override - public void onLoadFailed(Exception e, Drawable errorDrawable) { - super.onLoadFailed(e, errorDrawable); - mediaSession.setMetadata(metaData.build()); - } - - @Override - public void onResourceReady( - Bitmap resource, GlideAnimation glideAnimation) { - metaData.putBitmap( - MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); - mediaSession.setMetadata(metaData.build()); - } - }); - } - }); - } else { - mediaSession.setMetadata(metaData.build()); - } - } - - private void closeAudioEffectSession() { - final Intent audioEffectsIntent = - new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); - if (playback != null) { - audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); - } - audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); - sendBroadcast(audioEffectsIntent); - } - - private AudioManager getAudioManager() { - if (audioManager == null) { - audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - } - return audioManager; - } - - private void handleChangeInternal(@NonNull final String what) { - switch (what) { - case PLAY_STATE_CHANGED: - updateNotification(); - updateMediaSessionPlaybackState(); - final boolean isPlaying = isPlaying(); - if (!isPlaying && getSongProgressMillis() > 0) { - savePositionInTrack(); - } - songPlayCountHelper.notifyPlayStateChanged(isPlaying); - break; - case FAVORITE_STATE_CHANGED: - case META_CHANGED: - updateNotification(); - updateMediaSessionMetaData(); - savePosition(); - savePositionInTrack(); - final Song currentSong = getCurrentSong(); - if (currentSong != null) { - HistoryStore.getInstance(this).addSongId(currentSong.getId()); - } - if (songPlayCountHelper.shouldBumpPlayCount()) { - SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); - } - if (currentSong != null) { - songPlayCountHelper.notifySongChanged(currentSong); - } - break; - case QUEUE_CHANGED: - updateMediaSessionMetaData(); // because playing queue size might have changed - saveState(); - if (playingQueue.size() > 0) { - prepareNext(); - } else { - playingNotification.stop(); - } - break; - } - } - - private boolean openCurrent() { - synchronized (this) { - try { - if (playback != null) { - return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong()))); - } - } catch (Exception e) { - return false; - } - } - return false; - } - - private void playFromPlaylist(Intent intent) { - Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); - int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); - if (playlist != null) { - List playlistSongs = playlist.getSongs(); - if (!playlistSongs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - int startPosition = new Random().nextInt(playlistSongs.size()); - openQueue(playlistSongs, startPosition, true); - setShuffleMode(shuffleMode); - } else { - openQueue(playlistSongs, 0, true); - } - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG) - .show(); - } - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); - } - } - - private void prepareNext() { - playerHandler.removeMessages(PREPARE_NEXT); - playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); - } - - private void rePosition(int deletedPosition) { - int currentPosition = getPosition(); - if (deletedPosition < currentPosition) { - position = currentPosition - 1; - } else if (deletedPosition == currentPosition) { - if (playingQueue.size() > deletedPosition) { - setPosition(position); - } else { - setPosition(position - 1); - } - } - } - - private void registerBluetoothConnected() { - Log.i(TAG, "registerBluetoothConnected: "); - if (!bluetoothConnectedRegistered) { - registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter); - bluetoothConnectedRegistered = true; - } - } - - private void registerHeadsetEvents() { - if (!headsetReceiverRegistered && PreferenceUtil.INSTANCE.isHeadsetPlugged()) { - registerReceiver(headsetReceiver, headsetReceiverIntentFilter); - headsetReceiverRegistered = true; - } - } - - private void releaseResources() { - playerHandler.removeCallbacksAndMessages(null); - musicPlayerHandlerThread.quitSafely(); - queueSaveHandler.removeCallbacksAndMessages(null); - queueSaveHandlerThread.quitSafely(); - if (playback != null) { - playback.release(); - } - playback = null; - mediaSession.release(); - } - - private boolean requestFocus() { - return (getAudioManager() - .requestAudioFocus( - audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) - == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - } - - private void restoreState() { - shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); - repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); - handleAndSendChangeInternal(REPEAT_MODE_CHANGED); - - playerHandler.removeMessages(RESTORE_QUEUES); - playerHandler.sendEmptyMessage(RESTORE_QUEUES); - } - - private void savePosition() { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_POSITION, getPosition()) - .apply(); - } - - private void saveQueues() { - queueSaveHandler.removeMessages(SAVE_QUEUES); - queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); - } - - private void sendChangeInternal(final String what) { - sendBroadcast(new Intent(what)); - appWidgetBig.notifyChange(this, what); - appWidgetClassic.notifyChange(this, what); - appWidgetSmall.notifyChange(this, what); - appWidgetCard.notifyChange(this, what); - appWidgetText.notifyChange(this, what); - } - - private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { - int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE - if (getRepeatMode() == REPEAT_MODE_THIS) { - repeatIcon = R.drawable.ic_repeat_one; - } else if (getRepeatMode() == REPEAT_MODE_ALL) { - repeatIcon = R.drawable.ic_repeat_white_circle; - } - stateBuilder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon) - .build()); - - final int shuffleIcon = - getShuffleMode() == SHUFFLE_MODE_NONE - ? R.drawable.ic_shuffle_off_circled - : R.drawable.ic_shuffle_on_circled; - stateBuilder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon) - .build()); - - final int favoriteIcon = - MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong()) - ? R.drawable.ic_favorite - : R.drawable.ic_favorite_border; - stateBuilder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon) - .build()); - } - - private void setupMediaSession() { - ComponentName mediaButtonReceiverComponentName = - new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); - - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); - - PendingIntent mediaButtonReceiverPendingIntent = - PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); - - mediaSession = - new MediaSessionCompat( - this, - "RetroMusicPlayer", - mediaButtonReceiverComponentName, - mediaButtonReceiverPendingIntent); - MediaSessionCallback mediasessionCallback = - new MediaSessionCallback(getApplicationContext(), this); - mediaSession.setFlags( - MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS - | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); - mediaSession.setCallback(mediasessionCallback); - mediaSession.setActive(true); - mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); - } - - public class MusicBinder extends Binder { @NonNull - public MusicService getService() { - return MusicService.this; + public Song getCurrentSong() { + return getSongAt(getPosition()); + } + + @NonNull + public MediaSessionCompat getMediaSession() { + return mediaSession; + } + + public int getNextPosition(boolean force) { + int position = getPosition() + 1; + switch (getRepeatMode()) { + case REPEAT_MODE_ALL: + if (isLastTrack()) { + position = 0; + } + break; + case REPEAT_MODE_THIS: + if (force) { + if (isLastTrack()) { + position = 0; + } + } else { + position -= 1; + } + break; + default: + case REPEAT_MODE_NONE: + if (isLastTrack()) { + position -= 1; + } + break; + } + return position; + } + + @Nullable + public List getPlayingQueue() { + return playingQueue; + } + + public int getPosition() { + return position; + } + + public void setPosition(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(SET_POSITION); + playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); + } + + public int getPreviousPosition(boolean force) { + int newPosition = getPosition() - 1; + switch (repeatMode) { + case REPEAT_MODE_ALL: + if (newPosition < 0) { + if (getPlayingQueue() != null) { + newPosition = getPlayingQueue().size() - 1; + } + } + break; + case REPEAT_MODE_THIS: + if (force) { + if (newPosition < 0) { + if (getPlayingQueue() != null) { + newPosition = getPlayingQueue().size() - 1; + } + } + } else { + newPosition = getPosition(); + } + break; + default: + case REPEAT_MODE_NONE: + if (newPosition < 0) { + newPosition = 0; + } + break; + } + return newPosition; + } + + public long getQueueDurationMillis(int position) { + long duration = 0; + for (int i = position + 1; i < playingQueue.size(); i++) { + duration += playingQueue.get(i).getDuration(); + } + return duration; + } + + public int getRepeatMode() { + return repeatMode; + } + + public void setRepeatMode(final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_NONE: + case REPEAT_MODE_ALL: + case REPEAT_MODE_THIS: + this.repeatMode = repeatMode; + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_REPEAT_MODE, repeatMode) + .apply(); + prepareNext(); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + break; + } + } + + public int getShuffleMode() { + return shuffleMode; + } + + public void setShuffleMode(final int shuffleMode) { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_SHUFFLE_MODE, shuffleMode) + .apply(); + switch (shuffleMode) { + case SHUFFLE_MODE_SHUFFLE: + this.shuffleMode = shuffleMode; + if (this.getPlayingQueue() != null) { + ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition()); + } + position = 0; + break; + case SHUFFLE_MODE_NONE: + this.shuffleMode = shuffleMode; + long currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); + playingQueue = new ArrayList<>(originalPlayingQueue); + int newPosition = 0; + if (getPlayingQueue() != null) { + for (Song song : getPlayingQueue()) { + if (song.getId() == currentSongId) { + newPosition = getPlayingQueue().indexOf(song); + } + } + } + position = newPosition; + break; + } + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + notifyChange(QUEUE_CHANGED); + } + + @NonNull + public Song getSongAt(int position) { + if (position >= 0 && getPlayingQueue() != null && position < getPlayingQueue().size()) { + return getPlayingQueue().get(position); + } else { + return Song.Companion.getEmptySong(); + } + } + + public int getSongDurationMillis() { + if (playback != null) { + return playback.duration(); + } + return -1; + } + + public int getSongProgressMillis() { + if (playback != null) { + return playback.position(); + } + return -1; + } + + public void handleAndSendChangeInternal(@NonNull final String what) { + handleChangeInternal(what); + sendChangeInternal(what); + } + + public void initNotification() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && !PreferenceUtil.INSTANCE.isClassicNotification()) { + playingNotification = new PlayingNotificationImpl(); + } else { + playingNotification = new PlayingNotificationOreo(); + } + playingNotification.init(this); + } + + public boolean isLastTrack() { + if (getPlayingQueue() != null) { + return getPosition() == getPlayingQueue().size() - 1; + } + return false; + } + + public boolean isPausedByTransientLossOfFocus() { + return pausedByTransientLossOfFocus; + } + + public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) { + this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus; + } + + public boolean isPlaying() { + return playback != null && playback.isPlaying(); + } + + public void moveSong(int from, int to) { + if (from == to) { + return; + } + final int currentPosition = getPosition(); + Song songToMove = playingQueue.remove(from); + playingQueue.add(to, songToMove); + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + Song tmpSong = originalPlayingQueue.remove(from); + originalPlayingQueue.add(to, tmpSong); + } + if (from > currentPosition && to <= currentPosition) { + position = currentPosition + 1; + } else if (from < currentPosition && to >= currentPosition) { + position = currentPosition - 1; + } else if (from == currentPosition) { + position = to; + } + notifyChange(QUEUE_CHANGED); + } + + public void notifyChange(@NonNull final String what) { + handleAndSendChangeInternal(what); + sendPublicIntent(what); + } + + @NonNull + @Override + public IBinder onBind(Intent intent) { + return musicBind; + } + + @Override + public void onSharedPreferenceChanged( + @NonNull SharedPreferences sharedPreferences, @NonNull String key) { + switch (key) { + case GAP_LESS_PLAYBACK: + if (sharedPreferences.getBoolean(key, false)) { + prepareNext(); + } else { + if (playback != null) { + playback.setNextDataSource(null); + } + } + break; + case ALBUM_ART_ON_LOCK_SCREEN: + case BLURRED_ALBUM_ART: + updateMediaSessionMetaData(); + break; + case COLORED_NOTIFICATION: + updateNotification(); + break; + case CLASSIC_NOTIFICATION: + initNotification(); + updateNotification(); + break; + case TOGGLE_HEADSET: + registerHeadsetEvents(); + break; + } + } + + @Override + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + if (intent != null && intent.getAction() != null) { + restoreQueuesAndPositionIfNecessary(); + String action = intent.getAction(); + switch (action) { + case ACTION_TOGGLE_PAUSE: + if (isPlaying()) { + pause(); + } else { + play(); + } + break; + case ACTION_PAUSE: + pause(); + break; + case ACTION_PLAY: + play(); + break; + case ACTION_PLAY_PLAYLIST: + playFromPlaylist(intent); + break; + case ACTION_REWIND: + back(true); + break; + case ACTION_SKIP: + playNextSong(true); + break; + case ACTION_STOP: + case ACTION_QUIT: + pendingQuit = false; + quit(); + break; + case ACTION_PENDING_QUIT: + pendingQuit = true; + break; + case TOGGLE_FAVORITE: + MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong()); + break; + } + } + + return START_NOT_STICKY; + } + + @Override + public void onTrackEnded() { + acquireWakeLock(30000); + playerHandler.sendEmptyMessage(TRACK_ENDED); + } + + @Override + public void onTrackWentToNext() { + playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); + } + + @Override + public boolean onUnbind(Intent intent) { + if (!isPlaying()) { + stopSelf(); + } + return true; + } + + public void openQueue( + @Nullable final List playingQueue, + final int startPosition, + final boolean startPlaying) { + if (playingQueue != null + && !playingQueue.isEmpty() + && startPosition >= 0 + && startPosition < playingQueue.size()) { + // it is important to copy the playing queue here first as we might add/remove songs later + originalPlayingQueue = new ArrayList<>(playingQueue); + this.playingQueue = new ArrayList<>(originalPlayingQueue); + + int position = startPosition; + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition); + position = 0; + } + if (startPlaying) { + playSongAt(position); + } else { + setPosition(position); + } + notifyChange(QUEUE_CHANGED); + } + } + + public boolean openTrackAndPrepareNextAt(int position) { + synchronized (this) { + this.position = position; + boolean prepared = openCurrent(); + if (prepared) { + prepareNextImpl(); + } + notifyChange(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + return prepared; + } + } + + public void pause() { + pausedByTransientLossOfFocus = false; + if (playback != null && playback.isPlaying()) { + playback.pause(); + notifyChange(PLAY_STATE_CHANGED); + } + } + + public void play() { + synchronized (this) { + if (requestFocus()) { + if (playback != null && !playback.isPlaying()) { + if (!playback.isInitialized()) { + playSongAt(getPosition()); + } else { + playback.start(); + if (!becomingNoisyReceiverRegistered) { + registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); + becomingNoisyReceiverRegistered = true; + } + if (notHandledMetaChangedForCurrentTrack) { + handleChangeInternal(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + } + notifyChange(PLAY_STATE_CHANGED); + + // fixes a bug where the volume would stay ducked because the + // AudioManager.AUDIOFOCUS_GAIN event is not sent + playerHandler.removeMessages(DUCK); + playerHandler.sendEmptyMessage(UNDUCK); + } + } + } else { + Toast.makeText( + this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT) + .show(); + } + } + } + + public void playNextSong(boolean force) { + playSongAt(getNextPosition(force)); + } + + public void playPreviousSong(boolean force) { + playSongAt(getPreviousPosition(force)); + } + + public void playSongAt(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(PLAY_SONG); + playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); + } + + public void playSongAtImpl(int position) { + if (openTrackAndPrepareNextAt(position)) { + play(); + } else { + Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT) + .show(); + } + } + + public void playSongs(ArrayList songs, int shuffleMode) { + if (songs != null && !songs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition = new Random().nextInt(songs.size()); + openQueue(songs, startPosition, false); + setShuffleMode(shuffleMode); + } else { + openQueue(songs, 0, false); + } + play(); + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } + + public boolean prepareNextImpl() { + synchronized (this) { + try { + int nextPosition = getNextPosition(false); + if (playback != null) { + playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition)))); + } + this.nextPosition = nextPosition; + return true; + } catch (Exception e) { + return false; + } + } + } + + public void quit() { + pause(); + playingNotification.stop(); + + closeAudioEffectSession(); + getAudioManager().abandonAudioFocus(audioFocusListener); + stopSelf(); + } + + public void releaseWakeLock() { + if (wakeLock.isHeld()) { + wakeLock.release(); + } + } + + public void removeSong(int position) { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + playingQueue.remove(position); + originalPlayingQueue.remove(position); + } else { + originalPlayingQueue.remove(playingQueue.remove(position)); + } + + rePosition(position); + + notifyChange(QUEUE_CHANGED); + } + + public void removeSong(@NonNull Song song) { + for (int i = 0; i < playingQueue.size(); i++) { + if (playingQueue.get(i).getId() == song.getId()) { + playingQueue.remove(i); + rePosition(i); + } + } + for (int i = 0; i < originalPlayingQueue.size(); i++) { + if (originalPlayingQueue.get(i).getId() == song.getId()) { + originalPlayingQueue.remove(i); + } + } + notifyChange(QUEUE_CHANGED); + } + + public synchronized void restoreQueuesAndPositionIfNecessary() { + if (!queuesRestored && playingQueue.isEmpty()) { + List restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); + List restoredOriginalQueue = + MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); + int restoredPosition = + PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); + int restoredPositionInTrack = + PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); + + if (restoredQueue.size() > 0 + && restoredQueue.size() == restoredOriginalQueue.size() + && restoredPosition != -1) { + this.originalPlayingQueue = restoredOriginalQueue; + this.playingQueue = restoredQueue; + + position = restoredPosition; + openCurrent(); + prepareNext(); + + if (restoredPositionInTrack > 0) { + seek(restoredPositionInTrack); + } + + notHandledMetaChangedForCurrentTrack = true; + sendChangeInternal(META_CHANGED); + sendChangeInternal(QUEUE_CHANGED); + } + } + queuesRestored = true; + } + + public void runOnUiThread(Runnable runnable) { + uiThreadHandler.post(runnable); + } + + public void savePositionInTrack() { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()) + .apply(); + } + + public void saveQueuesImpl() { + MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); + } + + public void saveState() { + saveQueues(); + savePosition(); + savePositionInTrack(); + } + + public int seek(int millis) { + synchronized (this) { + try { + int newPosition = 0; + if (playback != null) { + newPosition = playback.seek(millis); + } + throttledSeekHandler.notifySeek(); + return newPosition; + } catch (Exception e) { + return -1; + } + } + } + + // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch + public void sendPublicIntent(@NonNull final String what) { + final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); + + final Song song = getCurrentSong(); + + if (song != null) { + intent.putExtra("id", song.getId()); + intent.putExtra("artist", song.getArtistName()); + intent.putExtra("album", song.getAlbumName()); + intent.putExtra("track", song.getTitle()); + intent.putExtra("duration", song.getDuration()); + intent.putExtra("position", (long) getSongProgressMillis()); + intent.putExtra("playing", isPlaying()); + intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); + sendStickyBroadcast(intent); + } + } + + public void toggleShuffle() { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + setShuffleMode(SHUFFLE_MODE_SHUFFLE); + } else { + setShuffleMode(SHUFFLE_MODE_NONE); + } + } + + public void updateMediaSessionPlaybackState() { + PlaybackStateCompat.Builder stateBuilder = + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState( + isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getSongProgressMillis(), + 1); + + setCustomAction(stateBuilder); + + mediaSession.setPlaybackState(stateBuilder.build()); + } + + public void updateNotification() { + if (playingNotification != null && getCurrentSong().getId() != -1) { + playingNotification.update(); + } + } + + public void updateMediaSessionMetaData() { + Log.i(TAG, "onResourceReady: "); + final Song song = getCurrentSong(); + + if (song.getId() == -1) { + mediaSession.setMetadata(null); + return; + } + + final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName()) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName()) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle()) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration()) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear()) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); + } + + if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) { + final Point screenSize = RetroUtil.getScreenSize(MusicService.this); + final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) + .checkIgnoreMediaStore(MusicService.this) + .asBitmap().build(); + if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { + request.transform(new BlurTransformation.Builder(MusicService.this).build()); + } + runOnUiThread(new Runnable() { + @Override + public void run() { + request.into(new SimpleTarget(screenSize.x, screenSize.y) { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + mediaSession.setMetadata(metaData.build()); + } + + @Override + public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + + metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); + mediaSession.setMetadata(metaData.build()); + } + }); + } + }); + } else { + mediaSession.setMetadata(metaData.build()); + } + } + + private void closeAudioEffectSession() { + final Intent audioEffectsIntent = + new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + if (playback != null) { + audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); + } + audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); + sendBroadcast(audioEffectsIntent); + } + + private AudioManager getAudioManager() { + if (audioManager == null) { + audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + return audioManager; + } + + private void handleChangeInternal(@NonNull final String what) { + switch (what) { + case PLAY_STATE_CHANGED: + updateNotification(); + updateMediaSessionPlaybackState(); + final boolean isPlaying = isPlaying(); + if (!isPlaying && getSongProgressMillis() > 0) { + savePositionInTrack(); + } + songPlayCountHelper.notifyPlayStateChanged(isPlaying); + break; + case FAVORITE_STATE_CHANGED: + case META_CHANGED: + updateNotification(); + updateMediaSessionMetaData(); + savePosition(); + savePositionInTrack(); + final Song currentSong = getCurrentSong(); + HistoryStore.getInstance(this).addSongId(currentSong.getId()); + if (songPlayCountHelper.shouldBumpPlayCount()) { + SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); + } + songPlayCountHelper.notifySongChanged(currentSong); + break; + case QUEUE_CHANGED: + updateMediaSessionMetaData(); // because playing queue size might have changed + saveState(); + if (playingQueue.size() > 0) { + prepareNext(); + } else { + playingNotification.stop(); + } + break; + } + } + + private boolean openCurrent() { + synchronized (this) { + try { + if (playback != null) { + return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong()))); + } + } catch (Exception e) { + return false; + } + } + return false; + } + + private void playFromPlaylist(Intent intent) { + Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); + int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); + if (playlist != null) { + List playlistSongs = playlist.getSongs(); + if (!playlistSongs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition = new Random().nextInt(playlistSongs.size()); + openQueue(playlistSongs, startPosition, true); + setShuffleMode(shuffleMode); + } else { + openQueue(playlistSongs, 0, true); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG) + .show(); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } + + private void prepareNext() { + playerHandler.removeMessages(PREPARE_NEXT); + playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); + } + + private void rePosition(int deletedPosition) { + int currentPosition = getPosition(); + if (deletedPosition < currentPosition) { + position = currentPosition - 1; + } else if (deletedPosition == currentPosition) { + if (playingQueue.size() > deletedPosition) { + setPosition(position); + } else { + setPosition(position - 1); + } + } + } + + private void registerBluetoothConnected() { + Log.i(TAG, "registerBluetoothConnected: "); + if (!bluetoothConnectedRegistered) { + registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter); + bluetoothConnectedRegistered = true; + } + } + + private void registerHeadsetEvents() { + if (!headsetReceiverRegistered && PreferenceUtil.INSTANCE.isHeadsetPlugged()) { + registerReceiver(headsetReceiver, headsetReceiverIntentFilter); + headsetReceiverRegistered = true; + } + } + + private void releaseResources() { + playerHandler.removeCallbacksAndMessages(null); + musicPlayerHandlerThread.quitSafely(); + queueSaveHandler.removeCallbacksAndMessages(null); + queueSaveHandlerThread.quitSafely(); + if (playback != null) { + playback.release(); + } + playback = null; + mediaSession.release(); + } + + private boolean requestFocus() { + return (getAudioManager() + .requestAudioFocus( + audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + } + + private void restoreState() { + shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); + repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + + playerHandler.removeMessages(RESTORE_QUEUES); + playerHandler.sendEmptyMessage(RESTORE_QUEUES); + } + + private void savePosition() { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION, getPosition()) + .apply(); + } + + private void saveQueues() { + queueSaveHandler.removeMessages(SAVE_QUEUES); + queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); + } + + private void sendChangeInternal(final String what) { + sendBroadcast(new Intent(what)); + appWidgetBig.notifyChange(this, what); + appWidgetClassic.notifyChange(this, what); + appWidgetSmall.notifyChange(this, what); + appWidgetCard.notifyChange(this, what); + appWidgetText.notifyChange(this, what); + } + + private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { + int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE + if (getRepeatMode() == REPEAT_MODE_THIS) { + repeatIcon = R.drawable.ic_repeat_one; + } else if (getRepeatMode() == REPEAT_MODE_ALL) { + repeatIcon = R.drawable.ic_repeat_white_circle; + } + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon) + .build()); + + final int shuffleIcon = + getShuffleMode() == SHUFFLE_MODE_NONE + ? R.drawable.ic_shuffle_off_circled + : R.drawable.ic_shuffle_on_circled; + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon) + .build()); + + final int favoriteIcon = + MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong()) + ? R.drawable.ic_favorite + : R.drawable.ic_favorite_border; + stateBuilder.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon) + .build()); + } + + private void setupMediaSession() { + ComponentName mediaButtonReceiverComponentName = + new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); + + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); + + PendingIntent mediaButtonReceiverPendingIntent = + PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + + mediaSession = + new MediaSessionCompat( + this, + "RetroMusicPlayer", + mediaButtonReceiverComponentName, + mediaButtonReceiverPendingIntent); + MediaSessionCallback mediasessionCallback = + new MediaSessionCallback(getApplicationContext(), this); + mediaSession.setFlags( + MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + mediaSession.setCallback(mediasessionCallback); + mediaSession.setActive(true); + mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); + } + + public class MusicBinder extends Binder { + + @NonNull + public MusicService getService() { + return MusicService.this; + } } - } } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt index 7bcbdfc9..d0dd3beb 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.kt @@ -8,7 +8,75 @@ import androidx.core.content.ContextCompat import androidx.core.content.edit import androidx.preference.PreferenceManager import androidx.viewpager.widget.ViewPager -import code.name.monkey.retromusic.* +import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP +import code.name.monkey.retromusic.ALBUM_ARTISTS_ONLY +import code.name.monkey.retromusic.ALBUM_ART_ON_LOCK_SCREEN +import code.name.monkey.retromusic.ALBUM_COVER_STYLE +import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM +import code.name.monkey.retromusic.ALBUM_DETAIL_SONG_SORT_ORDER +import code.name.monkey.retromusic.ALBUM_GRID_SIZE +import code.name.monkey.retromusic.ALBUM_GRID_SIZE_LAND +import code.name.monkey.retromusic.ALBUM_GRID_STYLE +import code.name.monkey.retromusic.ALBUM_SONG_SORT_ORDER +import code.name.monkey.retromusic.ALBUM_SORT_ORDER +import code.name.monkey.retromusic.ARTIST_ALBUM_SORT_ORDER +import code.name.monkey.retromusic.ARTIST_GRID_SIZE +import code.name.monkey.retromusic.ARTIST_GRID_SIZE_LAND +import code.name.monkey.retromusic.ARTIST_GRID_STYLE +import code.name.monkey.retromusic.ARTIST_SONG_SORT_ORDER +import code.name.monkey.retromusic.ARTIST_SORT_ORDER +import code.name.monkey.retromusic.AUDIO_DUCKING +import code.name.monkey.retromusic.AUTO_DOWNLOAD_IMAGES_POLICY +import code.name.monkey.retromusic.App +import code.name.monkey.retromusic.BLACK_THEME +import code.name.monkey.retromusic.BLUETOOTH_PLAYBACK +import code.name.monkey.retromusic.BLURRED_ALBUM_ART +import code.name.monkey.retromusic.CAROUSEL_EFFECT +import code.name.monkey.retromusic.CHOOSE_EQUALIZER +import code.name.monkey.retromusic.CLASSIC_NOTIFICATION +import code.name.monkey.retromusic.COLORED_APP_SHORTCUTS +import code.name.monkey.retromusic.COLORED_NOTIFICATION +import code.name.monkey.retromusic.DESATURATED_COLOR +import code.name.monkey.retromusic.EXPAND_NOW_PLAYING_PANEL +import code.name.monkey.retromusic.EXTRA_SONG_INFO +import code.name.monkey.retromusic.FILTER_SONG +import code.name.monkey.retromusic.GAP_LESS_PLAYBACK +import code.name.monkey.retromusic.GENERAL_THEME +import code.name.monkey.retromusic.GENRE_SORT_ORDER +import code.name.monkey.retromusic.HOME_ALBUM_GRID_STYLE +import code.name.monkey.retromusic.HOME_ARTIST_GRID_STYLE +import code.name.monkey.retromusic.IGNORE_MEDIA_STORE_ARTWORK +import code.name.monkey.retromusic.INITIALIZED_BLACKLIST +import code.name.monkey.retromusic.KEEP_SCREEN_ON +import code.name.monkey.retromusic.LANGUAGE_NAME +import code.name.monkey.retromusic.LAST_ADDED_CUTOFF +import code.name.monkey.retromusic.LAST_CHANGELOG_VERSION +import code.name.monkey.retromusic.LAST_PAGE +import code.name.monkey.retromusic.LAST_SLEEP_TIMER_VALUE +import code.name.monkey.retromusic.LIBRARY_CATEGORIES +import code.name.monkey.retromusic.LOCK_SCREEN +import code.name.monkey.retromusic.LYRICS_OPTIONS +import code.name.monkey.retromusic.NEXT_SLEEP_TIMER_ELAPSED_REALTIME +import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID +import code.name.monkey.retromusic.PAUSE_ON_ZERO_VOLUME +import code.name.monkey.retromusic.PLAYLIST_SORT_ORDER +import code.name.monkey.retromusic.R +import code.name.monkey.retromusic.RECENTLY_PLAYED_CUTOFF +import code.name.monkey.retromusic.SAF_SDCARD_URI +import code.name.monkey.retromusic.SLEEP_TIMER_FINISH_SONG +import code.name.monkey.retromusic.SONG_GRID_SIZE +import code.name.monkey.retromusic.SONG_GRID_SIZE_LAND +import code.name.monkey.retromusic.SONG_GRID_STYLE +import code.name.monkey.retromusic.SONG_SORT_ORDER +import code.name.monkey.retromusic.START_DIRECTORY +import code.name.monkey.retromusic.TAB_TEXT_MODE +import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS +import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN +import code.name.monkey.retromusic.TOGGLE_HEADSET +import code.name.monkey.retromusic.TOGGLE_HOME_BANNER +import code.name.monkey.retromusic.TOGGLE_SHUFFLE +import code.name.monkey.retromusic.TOGGLE_VOLUME +import code.name.monkey.retromusic.USER_NAME import code.name.monkey.retromusic.extensions.getIntRes import code.name.monkey.retromusic.extensions.getStringOrDefault import code.name.monkey.retromusic.fragments.AlbumCoverStyle @@ -16,7 +84,13 @@ import code.name.monkey.retromusic.fragments.NowPlayingScreen import code.name.monkey.retromusic.fragments.folder.FoldersFragment import code.name.monkey.retromusic.helper.SortOrder.* import code.name.monkey.retromusic.model.CategoryInfo -import code.name.monkey.retromusic.transform.* +import code.name.monkey.retromusic.transform.CascadingPageTransformer +import code.name.monkey.retromusic.transform.DepthTransformation +import code.name.monkey.retromusic.transform.HingeTransformation +import code.name.monkey.retromusic.transform.HorizontalFlipTransformation +import code.name.monkey.retromusic.transform.NormalPageTransformer +import code.name.monkey.retromusic.transform.VerticalFlipTransformation +import code.name.monkey.retromusic.transform.VerticalStackTransformer import code.name.monkey.retromusic.util.theme.ThemeMode import com.google.android.material.bottomnavigation.LabelVisibilityMode import com.google.gson.Gson @@ -154,6 +228,7 @@ object PreferenceUtil { putString(ALBUM_SORT_ORDER, value) } + var artistSortOrder get() = sharedPreferences.getStringOrDefault( ARTIST_SORT_ORDER, @@ -181,6 +256,15 @@ object PreferenceUtil { ArtistAlbumSortOrder.ALBUM_A_Z ) + var playlistSortOrder + get() = sharedPreferences.getStringOrDefault( + PLAYLIST_SORT_ORDER, + PlaylistSortOrder.PLAYLIST_A_Z + ) + set(value) = sharedPreferences.edit { + putString(PLAYLIST_SORT_ORDER, value) + } + val genreSortOrder get() = sharedPreferences.getStringOrDefault( GENRE_SORT_ORDER, @@ -413,7 +497,7 @@ object PreferenceUtil { val homeAlbumGridStyle: Int get() { - val position = sharedPreferences.getStringOrDefault(HOME_ALBUM_GRID_STYLE, "0").toInt() + val position = sharedPreferences.getStringOrDefault(HOME_ALBUM_GRID_STYLE, "4").toInt() val typedArray = App.getContext().resources.obtainTypedArray(R.array.pref_home_grid_style_layout) val layoutRes = typedArray.getResourceId(position, 0) diff --git a/app/src/main/res/drawable-night/ic_launcher_background.xml b/app/src/main/res/drawable-night/ic_launcher_background.xml new file mode 100644 index 00000000..c760e263 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml index da4d42d6..2417b12d 100644 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -2,70 +2,71 @@ xmlns:aapt="http://schemas.android.com/aapt" android:width="108dp" android:height="108dp" - android:viewportWidth="921.0526" - android:viewportHeight="921.0526"> - - - - - - - - - - - - - - - - - - - - - - - - - - + android:viewportWidth="108" + android:viewportHeight="108"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index b97f2835..d651a2a6 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -5,6 +5,6 @@ android:viewportWidth="108" android:viewportHeight="108"> diff --git a/app/src/main/res/layout/activity_lyrics.xml b/app/src/main/res/layout/activity_lyrics.xml index 3740113e..7facebba 100644 --- a/app/src/main/res/layout/activity_lyrics.xml +++ b/app/src/main/res/layout/activity_lyrics.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:id="@+id/container" android:layout_height="match_parent" android:transitionName="@string/transition_lyrics"> diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 036d09bc..7353dbd1 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09bc..7353dbd1 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 43bb965a..ad350707 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png deleted file mode 100644 index 94cbef3c..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index a1e8f4e0..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 43bb965a..ad350707 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index a8008b18..a5786f83 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png deleted file mode 100644 index 41be5687..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 3b2d6ffa..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index a8008b18..a5786f83 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 0fdcb0cb..7b5aa2c9 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png deleted file mode 100644 index c775d1c8..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 6ce2b8c4..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 0fdcb0cb..7b5aa2c9 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index cd7cb366..3523604e 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png deleted file mode 100644 index a42cd921..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 2acd41a3..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index cd7cb366..3523604e 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index fda6f29e..a57669d6 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png deleted file mode 100644 index 0282220d..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index f5f397f0..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index fda6f29e..a57669d6 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml index c5d5899f..236a57d3 100644 --- a/app/src/main/res/values/ic_launcher_background.xml +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #FFFFFF + #1C1C1C \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index e2cad59d..1020e9a0 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -16,6 +16,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7bb2c04d..4e789221 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -891,4 +891,7 @@ Import playlist It imports all playlists listed in the Android Media Store with songs, if the playlists already exists, the songs will get merged. Import + Song count + Ascending + Song count desc diff --git a/app/src/main/res/xml/pref_ui.xml b/app/src/main/res/xml/pref_ui.xml index 27cac1c8..54af8492 100644 --- a/app/src/main/res/xml/pref_ui.xml +++ b/app/src/main/res/xml/pref_ui.xml @@ -84,14 +84,14 @@