Rx Java and Normal compatible for all query
This commit is contained in:
parent
850036e5cc
commit
c2759e3ec0
89 changed files with 2900 additions and 1040 deletions
|
@ -121,7 +121,9 @@
|
||||||
android:name=".appshortcuts.AppShortcutLauncherActivity"
|
android:name=".appshortcuts.AppShortcutLauncherActivity"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
<activity android:name=".activities.saf.SAFGuideActivity" />
|
<activity
|
||||||
|
android:name=".activities.saf.SAFGuideActivity"
|
||||||
|
android:theme="@style/Theme.Intro" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.max_aspect"
|
android:name="android.max_aspect"
|
||||||
|
@ -240,16 +242,23 @@
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.MusicService"
|
android:name=".service.MusicService"
|
||||||
android:enabled="true" />
|
android:enabled="true"
|
||||||
<service
|
android:label="@string/app_name">
|
||||||
android:name=".service.WearBrowserService"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<!-- Android Auto -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.car.application"
|
||||||
|
android:resource="@xml/automotive_app_desc" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.car.application.theme"
|
||||||
|
android:resource="@style/CarTheme" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||||
|
android:resource="@drawable/ic_notification" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
|
@ -19,49 +19,6 @@ import android.provider.MediaStore
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"
|
|
||||||
@JvmField
|
|
||||||
val MUSIC_PACKAGE_NAME = "com.android.music"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_TOGGLE_PAUSE = "$RETRO_MUSIC_PACKAGE_NAME.togglepause"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_PLAY = "$RETRO_MUSIC_PACKAGE_NAME.play"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_PLAY_PLAYLIST = "$RETRO_MUSIC_PACKAGE_NAME.play.playlist"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_PAUSE = "$RETRO_MUSIC_PACKAGE_NAME.pause"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_STOP = "$RETRO_MUSIC_PACKAGE_NAME.stop"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_SKIP = "$RETRO_MUSIC_PACKAGE_NAME.skip"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_REWIND = "$RETRO_MUSIC_PACKAGE_NAME.rewind"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_QUIT = "$RETRO_MUSIC_PACKAGE_NAME.quitservice"
|
|
||||||
@JvmField
|
|
||||||
val ACTION_PENDING_QUIT = "$RETRO_MUSIC_PACKAGE_NAME.pendingquitservice"
|
|
||||||
@JvmField
|
|
||||||
val INTENT_EXTRA_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"
|
|
||||||
@JvmField
|
|
||||||
val INTENT_EXTRA_SHUFFLE_MODE = "$RETRO_MUSIC_PACKAGE_NAME.intentextra.shufflemode"
|
|
||||||
@JvmField
|
|
||||||
val APP_WIDGET_UPDATE = "$RETRO_MUSIC_PACKAGE_NAME.appwidgetupdate"
|
|
||||||
@JvmField
|
|
||||||
val EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val META_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.metachanged"
|
|
||||||
@JvmField
|
|
||||||
val QUEUE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.queuechanged"
|
|
||||||
@JvmField
|
|
||||||
val PLAY_STATE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.playstatechanged"
|
|
||||||
@JvmField
|
|
||||||
val REPEAT_MODE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.repeatmodechanged"
|
|
||||||
@JvmField
|
|
||||||
val SHUFFLE_MODE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.shufflemodechanged"
|
|
||||||
@JvmField
|
|
||||||
val MEDIA_STORE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.mediastorechanged"
|
|
||||||
const val RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
|
const val RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
|
||||||
const val TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534"
|
const val TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534"
|
||||||
const val GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer"
|
const val GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer"
|
||||||
|
@ -76,7 +33,6 @@ object Constants {
|
||||||
|
|
||||||
const val BASE_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
|
const val BASE_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val BASE_PROJECTION = arrayOf(BaseColumns._ID, // 0
|
val BASE_PROJECTION = arrayOf(BaseColumns._ID, // 0
|
||||||
MediaStore.Audio.AudioColumns.TITLE, // 1
|
MediaStore.Audio.AudioColumns.TITLE, // 1
|
||||||
MediaStore.Audio.AudioColumns.TRACK, // 2
|
MediaStore.Audio.AudioColumns.TRACK, // 2
|
||||||
|
|
|
@ -77,6 +77,9 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
||||||
|
|
||||||
ActivityCompat.postponeEnterTransition(this)
|
ActivityCompat.postponeEnterTransition(this)
|
||||||
|
|
||||||
|
|
||||||
|
artistImage = findViewById(R.id.artistImage)
|
||||||
|
|
||||||
val albumId = intent.getIntExtra(EXTRA_ALBUM_ID, -1)
|
val albumId = intent.getIntExtra(EXTRA_ALBUM_ID, -1)
|
||||||
albumDetailsPresenter = AlbumDetailsPresenter(this, albumId)
|
albumDetailsPresenter = AlbumDetailsPresenter(this, albumId)
|
||||||
albumDetailsPresenter.subscribe()
|
albumDetailsPresenter.subscribe()
|
||||||
|
@ -84,7 +87,6 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupToolbarMarginHeight()
|
setupToolbarMarginHeight()
|
||||||
|
|
||||||
artistImage = findViewById(R.id.artistImage)
|
|
||||||
artistImage.setOnClickListener {
|
artistImage.setOnClickListener {
|
||||||
val artistPairs = arrayOf<Pair<*, *>>(Pair.create(image, resources.getString(R.string.transition_artist_image)))
|
val artistPairs = arrayOf<Pair<*, *>>(Pair.create(image, resources.getString(R.string.transition_artist_image)))
|
||||||
NavigationUtil.goToArtist(this, album.artistId, *artistPairs)
|
NavigationUtil.goToArtist(this, album.artistId, *artistPairs)
|
||||||
|
@ -189,9 +191,7 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
||||||
private lateinit var artistImage: ImageView
|
private lateinit var artistImage: ImageView
|
||||||
|
|
||||||
private fun loadMoreFrom(album: Album) {
|
private fun loadMoreFrom(album: Album) {
|
||||||
disposable.add(ArtistLoader.getArtist(this, album.artistId)
|
disposable.add(ArtistLoader.getArtistFlowable(this, album.artistId)
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.map {
|
.map {
|
||||||
GlideApp.with(this@AlbumDetailsActivity)
|
GlideApp.with(this@AlbumDetailsActivity)
|
||||||
.asBitmapPalette()
|
.asBitmapPalette()
|
||||||
|
|
|
@ -6,14 +6,12 @@ import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
|
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
|
||||||
import code.name.monkey.retromusic.dialogs.OptionsSheetDialogFragment
|
|
||||||
import code.name.monkey.retromusic.fragments.mainactivity.LibraryFragment
|
import code.name.monkey.retromusic.fragments.mainactivity.LibraryFragment
|
||||||
import code.name.monkey.retromusic.fragments.mainactivity.folders.FoldersFragment
|
import code.name.monkey.retromusic.fragments.mainactivity.folders.FoldersFragment
|
||||||
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment
|
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment
|
||||||
|
@ -121,7 +119,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP
|
||||||
PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this)
|
PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCurrentFragment(fragment: Fragment, b: Boolean) {
|
private fun setCurrentFragment(fragment: Fragment, b: Boolean = false) {
|
||||||
val trans = supportFragmentManager.beginTransaction()
|
val trans = supportFragmentManager.beginTransaction()
|
||||||
trans.replace(R.id.fragment_container, fragment, null)
|
trans.replace(R.id.fragment_container, fragment, null)
|
||||||
if (b) {
|
if (b) {
|
||||||
|
@ -162,7 +160,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP
|
||||||
val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt()
|
val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt()
|
||||||
if (id >= 0) {
|
if (id >= 0) {
|
||||||
val position = intent.getIntExtra("position", 0)
|
val position = intent.getIntExtra("position", 0)
|
||||||
val songs = ArrayList(PlaylistSongsLoader.getPlaylistSongList(this, id).blockingFirst())
|
val songs = ArrayList(PlaylistSongsLoader.getPlaylistSongList(this, id))
|
||||||
MusicPlayerRemote.openQueue(songs, position, true)
|
MusicPlayerRemote.openQueue(songs, position, true)
|
||||||
handled = true
|
handled = true
|
||||||
}
|
}
|
||||||
|
@ -170,14 +168,14 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP
|
||||||
val id = parseIdFromIntent(intent, "albumId", "album").toInt()
|
val id = parseIdFromIntent(intent, "albumId", "album").toInt()
|
||||||
if (id >= 0) {
|
if (id >= 0) {
|
||||||
val position = intent.getIntExtra("position", 0)
|
val position = intent.getIntExtra("position", 0)
|
||||||
MusicPlayerRemote.openQueue(AlbumLoader.getAlbum(this, id).blockingFirst().songs!!, position, true)
|
MusicPlayerRemote.openQueue(AlbumLoader.getAlbum(this, id).songs!!, position, true)
|
||||||
handled = true
|
handled = true
|
||||||
}
|
}
|
||||||
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
|
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
|
||||||
val id = parseIdFromIntent(intent, "artistId", "artist").toInt()
|
val id = parseIdFromIntent(intent, "artistId", "artist").toInt()
|
||||||
if (id >= 0) {
|
if (id >= 0) {
|
||||||
val position = intent.getIntExtra("position", 0)
|
val position = intent.getIntExtra("position", 0)
|
||||||
MusicPlayerRemote.openQueue(ArtistLoader.getArtist(this, id).blockingFirst().songs, position, true)
|
MusicPlayerRemote.openQueue(ArtistLoader.getArtist(this, id).songs, position, true)
|
||||||
handled = true
|
handled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
|
||||||
// Playlist renamed
|
// Playlist renamed
|
||||||
val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist!!.id.toLong())
|
val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist!!.id.toLong())
|
||||||
if (playlistName != playlist!!.name) {
|
if (playlistName != playlist!!.name) {
|
||||||
playlist = PlaylistLoader.getPlaylist(this, playlist!!.id).blockingFirst()
|
playlist = PlaylistLoader.getPlaylist(this, playlist!!.id)
|
||||||
setToolbarTitle(playlist!!.name)
|
setToolbarTitle(playlist!!.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,7 +170,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, SearchCon
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showData(list: ArrayList<Any>) {
|
override fun showData(list: MutableList<Any>) {
|
||||||
searchAdapter!!.swapDataSet(list)
|
searchAdapter!!.swapDataSet(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,10 @@ import android.Manifest
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED
|
|
||||||
import code.name.monkey.retromusic.Constants.META_CHANGED
|
|
||||||
import code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED
|
|
||||||
import code.name.monkey.retromusic.Constants.QUEUE_CHANGED
|
|
||||||
import code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED
|
|
||||||
import code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
|
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSongPaths(): List<String> {
|
override fun getSongPaths(): List<String> {
|
||||||
val songs = AlbumLoader.getAlbum(this, id).blockingFirst().songs
|
val songs = AlbumLoader.getAlbum(this, id).songs
|
||||||
val paths = ArrayList<String>(songs!!.size)
|
val paths = ArrayList<String>(songs!!.size)
|
||||||
for (song in songs) {
|
for (song in songs) {
|
||||||
paths.add(song.data)
|
paths.add(song.data)
|
||||||
|
|
|
@ -108,7 +108,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
||||||
|
|
||||||
override fun getSongPaths(): List<String> {
|
override fun getSongPaths(): List<String> {
|
||||||
val paths = ArrayList<String>(1)
|
val paths = ArrayList<String>(1)
|
||||||
paths.add(SongLoader.getSong(this, id).blockingFirst().data)
|
paths.add(SongLoader.getSong(this, id).data)
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ class HomeAdapter(private val activity: AppCompatActivity, private var homes: Li
|
||||||
}
|
}
|
||||||
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
|
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||||
fun bindView(home: Home) {
|
fun bindView(home: Home) {
|
||||||
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, home.arrayList[0] as Playlist).blockingFirst()
|
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, home.arrayList[0] as Playlist)
|
||||||
recyclerView.apply {
|
recyclerView.apply {
|
||||||
val songAdapter = SongAdapter(activity, songs, R.layout.item_album_card, false, null)
|
val songAdapter = SongAdapter(activity, songs, R.layout.item_album_card, false, null)
|
||||||
layoutManager = GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
|
layoutManager = GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.util.*
|
||||||
|
|
||||||
class SearchAdapter(private val activity: AppCompatActivity, private var dataSet: List<Any>?) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
class SearchAdapter(private val activity: AppCompatActivity, private var dataSet: List<Any>?) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
||||||
|
|
||||||
fun swapDataSet(dataSet: ArrayList<Any>) {
|
fun swapDataSet(dataSet: MutableList<Any>) {
|
||||||
this.dataSet = dataSet
|
this.dataSet = dataSet
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,10 +149,9 @@ class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayL
|
||||||
val songs = ArrayList<Song>()
|
val songs = ArrayList<Song>()
|
||||||
for (playlist in playlists) {
|
for (playlist in playlists) {
|
||||||
if (playlist is AbsCustomPlaylist) {
|
if (playlist is AbsCustomPlaylist) {
|
||||||
songs.addAll(playlist.getSongs(activity).blockingFirst())
|
songs.addAll(playlist.getSongs(activity))
|
||||||
} else {
|
} else {
|
||||||
songs
|
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
|
||||||
.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id).blockingFirst())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return songs
|
return songs
|
||||||
|
@ -161,9 +160,9 @@ class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayL
|
||||||
private fun getSongs(playlist: Playlist): ArrayList<Song> {
|
private fun getSongs(playlist: Playlist): ArrayList<Song> {
|
||||||
val songs = ArrayList<Song>()
|
val songs = ArrayList<Song>()
|
||||||
if (playlist is AbsSmartPlaylist) {
|
if (playlist is AbsSmartPlaylist) {
|
||||||
songs.addAll(playlist.getSongs(activity).blockingFirst())
|
songs.addAll(playlist.getSongs(activity))
|
||||||
} else {
|
} else {
|
||||||
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id).blockingFirst())
|
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
|
||||||
}
|
}
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,7 @@ package code.name.monkey.retromusic.appshortcuts
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_PLAY_PLAYLIST
|
import code.name.monkey.retromusic.activities.SearchActivity
|
||||||
import code.name.monkey.retromusic.Constants.INTENT_EXTRA_PLAYLIST
|
|
||||||
import code.name.monkey.retromusic.Constants.INTENT_EXTRA_SHUFFLE_MODE
|
|
||||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType
|
import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType
|
||||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
|
||||||
|
@ -29,7 +27,7 @@ import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
|
||||||
import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist
|
import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist
|
||||||
import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
|
import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.SearchActivity
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
|
|
||||||
|
|
||||||
class AppShortcutLauncherActivity : Activity() {
|
class AppShortcutLauncherActivity : Activity() {
|
||||||
|
|
|
@ -24,14 +24,14 @@ import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.retromusic.Constants
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||||
import code.name.monkey.retromusic.glide.GlideApp
|
import code.name.monkey.retromusic.glide.GlideApp
|
||||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
@ -149,15 +149,15 @@ class AppWidgetBig : BaseAppWidget() {
|
||||||
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
|
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
|
||||||
|
|
||||||
// Previous track
|
// Previous track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||||
|
|
||||||
// Play and pause
|
// Play and pause
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||||
|
|
||||||
// Next track
|
// Next track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,15 +24,15 @@ import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.retromusic.Constants
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||||
import code.name.monkey.retromusic.glide.GlideApp
|
import code.name.monkey.retromusic.glide.GlideApp
|
||||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
@ -50,9 +50,9 @@ class AppWidgetCard : BaseAppWidget() {
|
||||||
|
|
||||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art)
|
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art)
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.Companion.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.Companion.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
|
|
||||||
linkButtons(context, appWidgetView)
|
linkButtons(context, appWidgetView)
|
||||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||||
|
@ -128,10 +128,10 @@ class AppWidgetCard : BaseAppWidget() {
|
||||||
val playPauseRest = if (isPlaying) R.drawable.ic_pause_white_24dp
|
val playPauseRest = if (isPlaying) R.drawable.ic_pause_white_24dp
|
||||||
else
|
else
|
||||||
R.drawable.ic_play_arrow_white_32dp
|
R.drawable.ic_play_arrow_white_32dp
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRest, color)!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRest, color)!!, 1f))
|
||||||
// Set prev/next button drawables
|
// Set prev/next button drawables
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, color)!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, color)!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color)!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color)!!, 1f))
|
||||||
|
|
||||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||||
val roundedBitmap = BaseAppWidget.Companion.createRoundedBitmap(image, imageSize, imageSize, cardRadius, 0f, cardRadius, 0f)
|
val roundedBitmap = BaseAppWidget.Companion.createRoundedBitmap(image, imageSize, imageSize, cardRadius, 0f, cardRadius, 0f)
|
||||||
|
@ -159,15 +159,15 @@ class AppWidgetCard : BaseAppWidget() {
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
// Previous track
|
// Previous track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||||
|
|
||||||
// Play and pause
|
// Play and pause
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||||
|
|
||||||
// Next track
|
// Next track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
@ -49,9 +50,9 @@ class AppWidgetClassic : BaseAppWidget() {
|
||||||
|
|
||||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art)
|
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art)
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
|
|
||||||
linkButtons(context, appWidgetView)
|
linkButtons(context, appWidgetView)
|
||||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||||
|
@ -116,11 +117,11 @@ class AppWidgetClassic : BaseAppWidget() {
|
||||||
else
|
else
|
||||||
R.drawable.ic_play_arrow_white_32dp
|
R.drawable.ic_play_arrow_white_32dp
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause,
|
||||||
BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color)!!, 1f))
|
createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color)!!, 1f))
|
||||||
|
|
||||||
// Set prev/next button drawables
|
// Set prev/next button drawables
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, color)!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, color)!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color)!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color)!!, 1f))
|
||||||
|
|
||||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||||
val roundedBitmap = BaseAppWidget.createRoundedBitmap(image, imageSize, imageSize,
|
val roundedBitmap = BaseAppWidget.createRoundedBitmap(image, imageSize, imageSize,
|
||||||
|
@ -149,15 +150,15 @@ class AppWidgetClassic : BaseAppWidget() {
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
// Previous track
|
// Previous track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||||
|
|
||||||
// Play and pause
|
// Play and pause
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||||
|
|
||||||
// Next track
|
// Next track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,15 +24,15 @@ import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.retromusic.Constants
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||||
import code.name.monkey.retromusic.glide.GlideApp
|
import code.name.monkey.retromusic.glide.GlideApp
|
||||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
@ -49,9 +49,9 @@ class AppWidgetSmall : BaseAppWidget() {
|
||||||
|
|
||||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||||
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art)
|
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art)
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||||
|
|
||||||
linkButtons(context, appWidgetView)
|
linkButtons(context, appWidgetView)
|
||||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||||
|
@ -153,15 +153,15 @@ class AppWidgetSmall : BaseAppWidget() {
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
// Previous track
|
// Previous track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||||
|
|
||||||
// Play and pause
|
// Play and pause
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||||
|
|
||||||
// Next track
|
// Next track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,20 +23,20 @@ import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import code.name.monkey.retromusic.App.Companion.context
|
import code.name.monkey.retromusic.App.Companion.context
|
||||||
import code.name.monkey.retromusic.Constants
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
|
|
||||||
class AppWidgetText : BaseAppWidget() {
|
class AppWidgetText : BaseAppWidget() {
|
||||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||||
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
|
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
|
||||||
|
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000)), 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000)), 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(context, R.color.md_white_1000)), 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(context, R.color.md_white_1000))!!, 1f))
|
||||||
|
|
||||||
appWidgetView.setTextColor(R.id.title, ContextCompat.getColor(context, R.color.md_white_1000))
|
appWidgetView.setTextColor(R.id.title, ContextCompat.getColor(context, R.color.md_white_1000))
|
||||||
appWidgetView.setTextColor(R.id.text, ContextCompat.getColor(context, R.color.md_white_1000))
|
appWidgetView.setTextColor(R.id.text, ContextCompat.getColor(context, R.color.md_white_1000))
|
||||||
|
@ -61,15 +61,15 @@ class AppWidgetText : BaseAppWidget() {
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
// Previous track
|
// Previous track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||||
|
|
||||||
// Play and pause
|
// Play and pause
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
|
||||||
|
|
||||||
// Next track
|
// Next track
|
||||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +95,9 @@ class AppWidgetText : BaseAppWidget() {
|
||||||
R.drawable.ic_pause_white_24dp
|
R.drawable.ic_pause_white_24dp
|
||||||
else
|
else
|
||||||
R.drawable.ic_play_arrow_white_32dp
|
R.drawable.ic_play_arrow_white_32dp
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, playPauseRes, ContextCompat.getColor(context, R.color.md_white_1000)), 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(context, playPauseRes, ContextCompat.getColor(context, R.color.md_white_1000))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_next, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000)), 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000))!!, 1f))
|
||||||
appWidgetView.setImageViewBitmap(R.id.button_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000)), 1f))
|
appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, ContextCompat.getColor(context, R.color.md_white_1000))!!, 1f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ import android.text.TextUtils
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import code.name.monkey.retromusic.App
|
import code.name.monkey.retromusic.App
|
||||||
import code.name.monkey.retromusic.Constants
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
|
|
||||||
abstract class BaseAppWidget : AppWidgetProvider() {
|
abstract class BaseAppWidget : AppWidgetProvider() {
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
||||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager,
|
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray) {
|
appWidgetIds: IntArray) {
|
||||||
defaultAppWidget(context, appWidgetIds)
|
defaultAppWidget(context, appWidgetIds)
|
||||||
val updateIntent = Intent(Constants.APP_WIDGET_UPDATE)
|
val updateIntent = Intent(APP_WIDGET_UPDATE)
|
||||||
updateIntent.putExtra(Constants.EXTRA_APP_WIDGET_NAME, NAME)
|
updateIntent.putExtra(EXTRA_APP_WIDGET_NAME, NAME)
|
||||||
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
||||||
updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
||||||
context.sendBroadcast(updateIntent)
|
context.sendBroadcast(updateIntent)
|
||||||
|
@ -54,7 +54,7 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
||||||
*/
|
*/
|
||||||
fun notifyChange(service: MusicService, what: String) {
|
fun notifyChange(service: MusicService, what: String) {
|
||||||
if (hasInstances(service)) {
|
if (hasInstances(service)) {
|
||||||
if (Constants.META_CHANGED == what || Constants.PLAY_STATE_CHANGED == what) {
|
if (META_CHANGED == what || PLAY_STATE_CHANGED == what) {
|
||||||
performUpdate(service, null)
|
performUpdate(service, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.auto;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Beesham Sarendranauth (Beesham)
|
||||||
|
*/
|
||||||
|
public class AutoMediaIDHelper {
|
||||||
|
|
||||||
|
// Media IDs used on browseable items of MediaBrowser
|
||||||
|
public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__";
|
||||||
|
public static final String MEDIA_ID_ROOT = "__ROOT__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; // TODO
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_HISTORY = "__BY_HISTORY__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_TOP_TRACKS = "__BY_TOP_TRACKS__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_PLAYLIST = "__BY_PLAYLIST__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_ALBUM = "__BY_ALBUM__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_ARTIST = "__BY_ARTIST__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_SHUFFLE = "__BY_SHUFFLE__";
|
||||||
|
public static final String MEDIA_ID_MUSICS_BY_QUEUE = "__BY_QUEUE__";
|
||||||
|
|
||||||
|
private static final String CATEGORY_SEPARATOR = "__/__";
|
||||||
|
private static final String LEAF_SEPARATOR = "__|__";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a String value that represents a playable or a browsable media.
|
||||||
|
* <p/>
|
||||||
|
* Encode the media browseable categories, if any, and the unique music ID, if any,
|
||||||
|
* into a single String mediaID.
|
||||||
|
* <p/>
|
||||||
|
* MediaIDs are of the form <categoryType>__/__<categoryValue>__|__<musicUniqueId>, to make it
|
||||||
|
* easy to find the category (like genre) that a music was selected from, so we
|
||||||
|
* can correctly build the playing queue. This is specially useful when
|
||||||
|
* one music can appear in more than one list, like "by genre -> genre_1"
|
||||||
|
* and "by artist -> artist_1".
|
||||||
|
*
|
||||||
|
* @param mediaID Unique ID for playable items, or null for browseable items.
|
||||||
|
* @param categories Hierarchy of categories representing this item's browsing parents.
|
||||||
|
* @return A hierarchy-aware media ID.
|
||||||
|
*/
|
||||||
|
public static String createMediaID(String mediaID, String... categories) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (categories != null) {
|
||||||
|
for (int i = 0; i < categories.length; i++) {
|
||||||
|
if (!isValidCategory(categories[i])) {
|
||||||
|
throw new IllegalArgumentException("Invalid category: " + categories[i]);
|
||||||
|
}
|
||||||
|
sb.append(categories[i]);
|
||||||
|
if (i < categories.length - 1) {
|
||||||
|
sb.append(CATEGORY_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mediaID != null) {
|
||||||
|
sb.append(LEAF_SEPARATOR).append(mediaID);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String extractCategory(@NonNull String mediaID) {
|
||||||
|
int pos = mediaID.indexOf(LEAF_SEPARATOR);
|
||||||
|
if (pos >= 0) {
|
||||||
|
return mediaID.substring(0, pos);
|
||||||
|
}
|
||||||
|
return mediaID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String extractMusicID(@NonNull String mediaID) {
|
||||||
|
int pos = mediaID.indexOf(LEAF_SEPARATOR);
|
||||||
|
if (pos >= 0) {
|
||||||
|
return mediaID.substring(pos + LEAF_SEPARATOR.length());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBrowseable(@NonNull String mediaID) {
|
||||||
|
return !mediaID.contains(LEAF_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidCategory(String category) {
|
||||||
|
return category == null ||
|
||||||
|
(!category.contains(CATEGORY_SEPARATOR) && !category.contains(LEAF_SEPARATOR));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.auto;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.support.v4.media.MediaBrowserCompat;
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
|
||||||
|
import code.name.monkey.appthemehelper.ThemeStore;
|
||||||
|
import code.name.monkey.retromusic.R;
|
||||||
|
import code.name.monkey.retromusic.loaders.AlbumLoader;
|
||||||
|
import code.name.monkey.retromusic.loaders.ArtistLoader;
|
||||||
|
import code.name.monkey.retromusic.loaders.PlaylistLoader;
|
||||||
|
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
||||||
|
import code.name.monkey.retromusic.model.Album;
|
||||||
|
import code.name.monkey.retromusic.model.Artist;
|
||||||
|
import code.name.monkey.retromusic.model.Playlist;
|
||||||
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore;
|
||||||
|
import code.name.monkey.retromusic.service.MusicService;
|
||||||
|
import code.name.monkey.retromusic.util.ImageUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Beesham Sarendranauth (Beesham)
|
||||||
|
*/
|
||||||
|
public class AutoMusicProvider {
|
||||||
|
|
||||||
|
private static final String TAG = AutoMusicProvider.class.getName();
|
||||||
|
|
||||||
|
private static final String BASE_URI = "androidauto://phonograph";
|
||||||
|
private static final int PATH_SEGMENT_ID = 0;
|
||||||
|
private static final int PATH_SEGMENT_TITLE = 1;
|
||||||
|
private static final int PATH_SEGMENT_ARTIST = 2;
|
||||||
|
private static final int PATH_SEGMENT_ALBUM_ID = 3;
|
||||||
|
|
||||||
|
private WeakReference<MusicService> mMusicService;
|
||||||
|
|
||||||
|
// Categorized caches for music data
|
||||||
|
private ConcurrentMap<Integer, Uri> mMusicListByHistory;
|
||||||
|
private ConcurrentMap<Integer, Uri> mMusicListByTopTracks;
|
||||||
|
private ConcurrentMap<Integer, Uri> mMusicListByPlaylist;
|
||||||
|
private ConcurrentMap<Integer, Uri> mMusicListByAlbum;
|
||||||
|
private ConcurrentMap<Integer, Uri> mMusicListByArtist;
|
||||||
|
|
||||||
|
private Uri defaultAlbumArtUri;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private volatile State mCurrentState = State.NON_INITIALIZED;
|
||||||
|
|
||||||
|
public AutoMusicProvider(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
mMusicListByHistory = new ConcurrentSkipListMap<>();
|
||||||
|
mMusicListByTopTracks = new ConcurrentSkipListMap<>();
|
||||||
|
mMusicListByPlaylist = new ConcurrentSkipListMap<>();
|
||||||
|
mMusicListByAlbum = new ConcurrentSkipListMap<>();
|
||||||
|
mMusicListByArtist = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
defaultAlbumArtUri = Uri.parse("android.resource://" +
|
||||||
|
mContext.getPackageName() + "/drawable/" +
|
||||||
|
mContext.getResources().getResourceEntryName(R.drawable.default_album_art));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Uri> getHistory() {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return mMusicListByHistory.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Uri> getTopTracks() {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return mMusicListByTopTracks.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Uri> getPlaylists() {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return mMusicListByPlaylist.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Uri> getAlbums() {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return mMusicListByAlbum.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Uri> getArtists() {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return mMusicListByArtist.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Uri> getQueue() {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-built every time since the queue updates often
|
||||||
|
ConcurrentMap<Integer, Uri> queueList = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
if (mContext != null) {
|
||||||
|
final List<Song> songs = MusicPlaybackQueueStore.getInstance(mContext).getSavedOriginalPlayingQueue();
|
||||||
|
for (int i = 0; i < songs.size(); i++) {
|
||||||
|
final Song s = songs.get(i);
|
||||||
|
Uri.Builder topTracksData = Uri.parse(BASE_URI).buildUpon();
|
||||||
|
topTracksData.appendPath(String.valueOf(s.getId()))
|
||||||
|
.appendPath(s.getTitle())
|
||||||
|
.appendPath(s.getArtistName())
|
||||||
|
.appendPath(String.valueOf(s.getAlbumId()));
|
||||||
|
queueList.putIfAbsent(i, topTracksData.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return queueList.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return mCurrentState == State.INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of music tracks from a server and caches the track information
|
||||||
|
* for future reference, keying tracks by musicId
|
||||||
|
*/
|
||||||
|
public void retrieveMediaAsync(final Callback callback) {
|
||||||
|
if (mCurrentState == State.INITIALIZED) {
|
||||||
|
if (callback != null) {
|
||||||
|
// Nothing to do, execute callback immediately
|
||||||
|
callback.onMusicCatalogReady(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronously load the music catalog in a separate thread
|
||||||
|
new AsyncTask<Void, Void, State>() {
|
||||||
|
@Override
|
||||||
|
protected State doInBackground(Void... params) {
|
||||||
|
retrieveMedia();
|
||||||
|
return mCurrentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(State current) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onMusicCatalogReady(current == State.INITIALIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void buildListsByHistory() {
|
||||||
|
ConcurrentMap<Integer, Uri> newMusicListByHistory = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
final List<Song> songs = TopAndRecentlyPlayedTracksLoader.INSTANCE.getRecentlyPlayedTracks(mContext);
|
||||||
|
for (int i = 0; i < songs.size(); i++) {
|
||||||
|
final Song s = songs.get(i);
|
||||||
|
Uri.Builder topTracksData = Uri.parse(BASE_URI).buildUpon();
|
||||||
|
topTracksData.appendPath(String.valueOf(s.getId()))
|
||||||
|
.appendPath(s.getTitle())
|
||||||
|
.appendPath(s.getArtistName())
|
||||||
|
.appendPath(String.valueOf(s.getAlbumId()));
|
||||||
|
newMusicListByHistory.putIfAbsent(i, topTracksData.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
mMusicListByHistory = newMusicListByHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void buildListsByTopTracks() {
|
||||||
|
ConcurrentMap<Integer, Uri> newMusicListByTopTracks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
final List<Song> songs = TopAndRecentlyPlayedTracksLoader.INSTANCE.getTopTracks(mContext);
|
||||||
|
for (int i = 0; i < songs.size(); i++) {
|
||||||
|
final Song s = songs.get(i);
|
||||||
|
Uri.Builder topTracksData = Uri.parse(BASE_URI).buildUpon();
|
||||||
|
topTracksData.appendPath(String.valueOf(s.getId()))
|
||||||
|
.appendPath(s.getTitle())
|
||||||
|
.appendPath(s.getArtistName())
|
||||||
|
.appendPath(String.valueOf(s.getAlbumId()));
|
||||||
|
newMusicListByTopTracks.putIfAbsent(i, topTracksData.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
mMusicListByTopTracks = newMusicListByTopTracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void buildListsByPlaylist() {
|
||||||
|
ConcurrentMap<Integer, Uri> newMusicListByPlaylist = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
final List<Playlist> playlists = PlaylistLoader.INSTANCE.getAllPlaylists(mContext);
|
||||||
|
for (int i = 0; i < playlists.size(); i++) {
|
||||||
|
final Playlist p = playlists.get(i);
|
||||||
|
Uri.Builder playlistData = Uri.parse(BASE_URI).buildUpon();
|
||||||
|
playlistData.appendPath(String.valueOf(p.id))
|
||||||
|
.appendPath(p.name);
|
||||||
|
newMusicListByPlaylist.putIfAbsent(i, playlistData.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
mMusicListByPlaylist = newMusicListByPlaylist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void buildListsByAlbum() {
|
||||||
|
ConcurrentMap<Integer, Uri> newMusicListByAlbum = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
final List<Album> albums = AlbumLoader.INSTANCE.getAllAlbums(mContext);
|
||||||
|
for (int i = 0; i < albums.size(); i++) {
|
||||||
|
final Album a = albums.get(i);
|
||||||
|
Uri.Builder albumData = Uri.parse(BASE_URI).buildUpon();
|
||||||
|
albumData.appendPath(String.valueOf(a.getId()))
|
||||||
|
.appendPath(a.getTitle())
|
||||||
|
.appendPath(a.getArtistName())
|
||||||
|
.appendPath(String.valueOf(a.getId()));
|
||||||
|
newMusicListByAlbum.putIfAbsent(i, albumData.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
mMusicListByAlbum = newMusicListByAlbum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void buildListsByArtist() {
|
||||||
|
ConcurrentMap<Integer, Uri> newMusicListByArtist = new ConcurrentSkipListMap<>();
|
||||||
|
|
||||||
|
final List<Artist> artists = ArtistLoader.INSTANCE.getAllArtists(mContext);
|
||||||
|
for (int i = 0; i < artists.size(); i++) {
|
||||||
|
final Artist a = artists.get(i);
|
||||||
|
Uri.Builder artistData = Uri.parse(BASE_URI).buildUpon();
|
||||||
|
artistData.appendPath(String.valueOf(a.getId()))
|
||||||
|
.appendPath(a.getName())
|
||||||
|
.appendPath(a.getName());
|
||||||
|
newMusicListByArtist.putIfAbsent(i, artistData.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
mMusicListByArtist = newMusicListByArtist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void retrieveMedia() {
|
||||||
|
try {
|
||||||
|
if (mCurrentState == State.NON_INITIALIZED) {
|
||||||
|
mCurrentState = State.INITIALIZING;
|
||||||
|
|
||||||
|
buildListsByHistory();
|
||||||
|
buildListsByTopTracks();
|
||||||
|
buildListsByPlaylist();
|
||||||
|
buildListsByAlbum();
|
||||||
|
buildListsByArtist();
|
||||||
|
mCurrentState = State.INITIALIZED;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mCurrentState != State.INITIALIZED) {
|
||||||
|
// Something bad happened, so we reset state to NON_INITIALIZED to allow
|
||||||
|
// retries (eg if the network connection is temporary unavailable)
|
||||||
|
mCurrentState = State.NON_INITIALIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MediaBrowserCompat.MediaItem> getChildren(String mediaId, Resources resources) {
|
||||||
|
List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!AutoMediaIDHelper.isBrowseable(mediaId)) {
|
||||||
|
return mediaItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mediaId) {
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_ROOT:
|
||||||
|
mediaItems.add(createBrowsableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY, resources.getString(R.string.history_label), R.drawable.ic_access_time_white_24dp));
|
||||||
|
mediaItems.add(createBrowsableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS, resources.getString(R.string.top_tracks_label), R.drawable.ic_trending_up_white_24dp));
|
||||||
|
mediaItems.add(createBrowsableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST, resources.getString(R.string.playlists_label), R.drawable.ic_queue_music_white_24dp));
|
||||||
|
mediaItems.add(createBrowsableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM, resources.getString(R.string.albums_label), R.drawable.ic_album_white_24dp));
|
||||||
|
mediaItems.add(createBrowsableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST, resources.getString(R.string.artists_label), R.drawable.ic_artist_white_24dp));
|
||||||
|
mediaItems.add(createPlayableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE, resources.getString(R.string.action_shuffle_all), R.drawable.ic_shuffle_white_24dp, null));
|
||||||
|
mediaItems.add(createBrowsableMediaItem(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE, resources.getString(R.string.queue_label), R.drawable.ic_playlist_play_white_24dp));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY:
|
||||||
|
for (final Uri uri : getHistory()) {
|
||||||
|
mediaItems.add(createPlayableMediaItem(mediaId, uri, uri.getPathSegments().get(PATH_SEGMENT_TITLE), uri.getPathSegments().get(PATH_SEGMENT_ARTIST)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS:
|
||||||
|
for (final Uri uri : getTopTracks()) {
|
||||||
|
mediaItems.add(createPlayableMediaItem(mediaId, uri, uri.getPathSegments().get(PATH_SEGMENT_TITLE), uri.getPathSegments().get(PATH_SEGMENT_ARTIST)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST:
|
||||||
|
for (final Uri uri : getPlaylists()) {
|
||||||
|
mediaItems.add(createPlayableMediaItem(mediaId, uri, uri.getPathSegments().get(PATH_SEGMENT_TITLE), null));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM:
|
||||||
|
for (final Uri uri : getAlbums()) {
|
||||||
|
mediaItems.add(createPlayableMediaItem(mediaId, uri, uri.getPathSegments().get(PATH_SEGMENT_TITLE), uri.getPathSegments().get(PATH_SEGMENT_ARTIST)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST:
|
||||||
|
for (final Uri uri : getArtists()) {
|
||||||
|
mediaItems.add(createPlayableMediaItem(mediaId, uri, uri.getPathSegments().get(PATH_SEGMENT_ARTIST), null));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE:
|
||||||
|
// TODO: auto scroll to current track, indicate that it's playing
|
||||||
|
for (final Uri uri : getQueue()) {
|
||||||
|
mediaItems.add(createPlayableMediaItem(mediaId, uri, uri.getPathSegments().get(PATH_SEGMENT_TITLE), uri.getPathSegments().get(PATH_SEGMENT_ARTIST)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaBrowserCompat.MediaItem createBrowsableMediaItem(String mediaId, String title, int iconDrawableId) {
|
||||||
|
MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder();
|
||||||
|
builder.setMediaId(mediaId)
|
||||||
|
.setTitle(title)
|
||||||
|
.setIconBitmap(ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(mContext, iconDrawableId, ThemeStore.Companion.textColorSecondary(mContext))));
|
||||||
|
|
||||||
|
return new MediaBrowserCompat.MediaItem(builder.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaBrowserCompat.MediaItem createPlayableMediaItem(String mediaId, Uri musicSelection,
|
||||||
|
String title, @Nullable String subtitle) {
|
||||||
|
return createPlayableMediaItem(mediaId, musicSelection, title, subtitle, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaBrowserCompat.MediaItem createPlayableMediaItem(String mediaId, Uri musicSelection,
|
||||||
|
String title, @Nullable String subtitle,
|
||||||
|
@Nullable Bitmap albumArt, @Nullable Resources resources) {
|
||||||
|
MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder();
|
||||||
|
builder.setMediaId(AutoMediaIDHelper.createMediaID(musicSelection.getPathSegments().get(PATH_SEGMENT_ID), mediaId))
|
||||||
|
.setTitle(title);
|
||||||
|
|
||||||
|
if (subtitle != null) {
|
||||||
|
builder.setSubtitle(subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resources != null) {
|
||||||
|
if (albumArt != null) {
|
||||||
|
builder.setIconBitmap(albumArt);
|
||||||
|
} else {
|
||||||
|
builder.setIconUri(defaultAlbumArtUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MediaBrowserCompat.MediaItem(builder.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaBrowserCompat.MediaItem createPlayableMediaItem(String mediaId, String title, int iconDrawableId,
|
||||||
|
@Nullable String subtitle) {
|
||||||
|
MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder()
|
||||||
|
.setMediaId(mediaId)
|
||||||
|
.setTitle(title)
|
||||||
|
.setIconBitmap(ImageUtil.createBitmap(ImageUtil.getTintedVectorDrawable(mContext, iconDrawableId, ThemeStore.Companion.textColorSecondary(mContext))));
|
||||||
|
|
||||||
|
if (subtitle != null) {
|
||||||
|
builder.setSubtitle(subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MediaBrowserCompat.MediaItem(builder.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
NON_INITIALIZED, INITIALIZING, INITIALIZED
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onMusicCatalogReady(boolean success);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,6 @@ import code.name.monkey.retromusic.util.PlaylistsUtil
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
import kotlinx.android.synthetic.main.activity_user_info.*
|
|
||||||
|
|
||||||
|
|
||||||
class AddToPlaylistDialog : DialogFragment() {
|
class AddToPlaylistDialog : DialogFragment() {
|
||||||
|
@ -33,7 +32,7 @@ class AddToPlaylistDialog : DialogFragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): Dialog {
|
): Dialog {
|
||||||
val cntx = requireContext()
|
val cntx = requireContext()
|
||||||
val playlists = PlaylistLoader.getAllPlaylists(cntx).blockingFirst()
|
val playlists = PlaylistLoader.getAllPlaylists(cntx)
|
||||||
val playlistNames: MutableList<String> = mutableListOf()
|
val playlistNames: MutableList<String> = mutableListOf()
|
||||||
playlistNames.add(cntx.resources.getString(R.string.action_new_playlist))
|
playlistNames.add(cntx.resources.getString(R.string.action_new_playlist))
|
||||||
for (p in playlists) {
|
for (p in playlists) {
|
||||||
|
|
|
@ -18,7 +18,6 @@ import android.app.AlarmManager
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
|
@ -29,10 +28,11 @@ import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import code.name.monkey.appthemehelper.ThemeStore
|
import code.name.monkey.appthemehelper.ThemeStore
|
||||||
import code.name.monkey.retromusic.Constants
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT
|
||||||
import code.name.monkey.retromusic.util.MusicUtil
|
import code.name.monkey.retromusic.util.MusicUtil
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.ViewUtil
|
import code.name.monkey.retromusic.util.ViewUtil
|
||||||
|
@ -153,8 +153,8 @@ class SleepTimerDialog : DialogFragment() {
|
||||||
private fun makeTimerIntent(): Intent {
|
private fun makeTimerIntent(): Intent {
|
||||||
val intent = Intent(activity, MusicService::class.java)
|
val intent = Intent(activity, MusicService::class.java)
|
||||||
return if (shouldFinishLastSong.isChecked) {
|
return if (shouldFinishLastSong.isChecked) {
|
||||||
intent.setAction(Constants.ACTION_PENDING_QUIT)
|
intent.setAction(ACTION_PENDING_QUIT)
|
||||||
} else intent.setAction(Constants.ACTION_QUIT)
|
} else intent.setAction(ACTION_QUIT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -572,7 +572,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FileUtil.matchFilesWithMediaStore(context, files).blockingFirst();
|
return FileUtil.matchFilesWithMediaStore(context, files);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
cancel(false);
|
cancel(false);
|
||||||
|
|
|
@ -105,7 +105,7 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
|
||||||
}
|
}
|
||||||
|
|
||||||
actionShuffle.setOnClickListener {
|
actionShuffle.setOnClickListener {
|
||||||
MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(mainActivity).blockingFirst(), true)
|
MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(mainActivity) , true)
|
||||||
}
|
}
|
||||||
|
|
||||||
history.setOnClickListener {
|
history.setOnClickListener {
|
||||||
|
|
|
@ -213,7 +213,7 @@ class FullPlayerFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbac
|
||||||
private val compositeDisposable = CompositeDisposable()
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
private fun updateArtistImage() {
|
private fun updateArtistImage() {
|
||||||
compositeDisposable.addAll(ArtistLoader.getArtist(context!!, MusicPlayerRemote.currentSong.artistId)
|
compositeDisposable.addAll(ArtistLoader.getArtistFlowable(context!!, MusicPlayerRemote.currentSong.artistId)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
|
|
|
@ -12,13 +12,11 @@
|
||||||
* See the GNU General Public License for more details.
|
* See the GNU General Public License for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package code.name.monkey.retromusic.helper
|
package code.name.monkey.retromusic.helper;
|
||||||
|
|
||||||
interface M3UConstants {
|
public interface M3UConstants {
|
||||||
companion object {
|
String EXTENSION = "m3u";
|
||||||
val EXTENSION = "m3u"
|
String HEADER = "#EXTM3U";
|
||||||
val HEADER = "#EXTM3U"
|
String ENTRY = "#EXTINF:";
|
||||||
val ENTRY = "#EXTINF:"
|
String DURATION_SEPARATOR = ",";
|
||||||
val DURATION_SEPARATOR = ","
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -16,47 +16,43 @@ package code.name.monkey.retromusic.helper;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
|
|
||||||
import code.name.monkey.retromusic.model.AbsCustomPlaylist;
|
|
||||||
import code.name.monkey.retromusic.model.Playlist;
|
import code.name.monkey.retromusic.model.Playlist;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import io.reactivex.Observable;
|
|
||||||
|
|
||||||
public class M3UWriter implements M3UConstants {
|
public class M3UWriter implements M3UConstants {
|
||||||
|
|
||||||
public static Observable<File> write(Context context, File dir, Playlist playlist) throws IOException {
|
@Nullable
|
||||||
|
public static File write(@NonNull Context context,
|
||||||
|
@NonNull File dir,
|
||||||
|
@NonNull Playlist playlist) throws IOException {
|
||||||
if (!dir.exists()) //noinspection ResultOfMethodCallIgnored
|
if (!dir.exists()) //noinspection ResultOfMethodCallIgnored
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
File file = new File(dir, playlist.name.concat("." + M3UConstants.Companion.getEXTENSION()));
|
File file = new File(dir, playlist.name.concat("." + EXTENSION));
|
||||||
|
|
||||||
ArrayList<? extends Song> songs;
|
|
||||||
if (playlist instanceof AbsCustomPlaylist) {
|
|
||||||
songs = ((AbsCustomPlaylist) playlist).getSongs(context).blockingFirst();
|
|
||||||
} else {
|
|
||||||
songs = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(context, playlist.id).blockingFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ArrayList<Song> songs = playlist.getSongs(context);
|
||||||
|
|
||||||
if (songs.size() > 0) {
|
if (songs.size() > 0) {
|
||||||
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
|
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
|
||||||
|
|
||||||
bw.write(M3UConstants.Companion.getHEADER());
|
bw.write(HEADER);
|
||||||
for (Song song : songs) {
|
for (Song song : songs) {
|
||||||
bw.newLine();
|
bw.newLine();
|
||||||
bw.write(M3UConstants.Companion.getENTRY() + song.getDuration() + M3UConstants.Companion.getDURATION_SEPARATOR() + song.getArtistName() + " - " + song.getTitle());
|
bw.write(ENTRY + song.getDuration() + DURATION_SEPARATOR + song.getArtistName() + " - " + song.getTitle());
|
||||||
bw.newLine();
|
bw.newLine();
|
||||||
bw.write(song.getData());
|
bw.write(song.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
bw.close();
|
bw.close();
|
||||||
}
|
}
|
||||||
|
return file;
|
||||||
return Observable.just(file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ import code.name.monkey.retromusic.loaders.SongLoader
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -366,6 +365,7 @@ object MusicPlayerRemote {
|
||||||
|
|
||||||
fun playFromUri(uri: Uri) {
|
fun playFromUri(uri: Uri) {
|
||||||
if (musicService != null) {
|
if (musicService != null) {
|
||||||
|
|
||||||
var songs: ArrayList<Song>? = null
|
var songs: ArrayList<Song>? = null
|
||||||
if (uri.scheme != null && uri.authority != null) {
|
if (uri.scheme != null && uri.authority != null) {
|
||||||
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
|
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
|
||||||
|
@ -376,23 +376,18 @@ object MusicPlayerRemote {
|
||||||
songId = uri.lastPathSegment
|
songId = uri.lastPathSegment
|
||||||
}
|
}
|
||||||
if (songId != null) {
|
if (songId != null) {
|
||||||
/* songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
|
||||||
musicService,
|
|
||||||
MediaStore.Audio.AudioColumns._ID + "=?",
|
|
||||||
new String[]{songId}
|
|
||||||
));*/
|
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
musicService!!,
|
musicService!!,
|
||||||
MediaStore.Audio.AudioColumns._ID + "=?",
|
MediaStore.Audio.AudioColumns._ID + "=?",
|
||||||
arrayOf(songId)))
|
arrayOf(songId)
|
||||||
.subscribeOn(Schedulers.io()).blockingFirst()
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
var songFile: File? = null
|
var songFile: File? = null
|
||||||
if (uri.authority != null && uri.authority == "com.android.externalstorage.documents") {
|
if (uri.authority != null && uri.authority == "com.android.externalstorage.documents") {
|
||||||
songFile = File(Environment.getExternalStorageDirectory(), uri.path!!.split(":".toRegex(), 2).toTypedArray()[1])
|
songFile = File(Environment.getExternalStorageDirectory(), uri.path?.split(":".toRegex(), 2)?.get(1))
|
||||||
}
|
}
|
||||||
if (songFile == null) {
|
if (songFile == null) {
|
||||||
val path = getFilePathFromUri(musicService!!, uri)
|
val path = getFilePathFromUri(musicService!!, uri)
|
||||||
|
@ -403,8 +398,11 @@ object MusicPlayerRemote {
|
||||||
songFile = File(uri.path)
|
songFile = File(uri.path)
|
||||||
}
|
}
|
||||||
if (songFile != null) {
|
if (songFile != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(musicService!!, MediaStore.Audio.AudioColumns.DATA + "=?", arrayOf(songFile.absolutePath)
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
)).blockingFirst()
|
musicService!!,
|
||||||
|
MediaStore.Audio.AudioColumns.DATA + "=?",
|
||||||
|
arrayOf(songFile.absolutePath)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (songs != null && songs.isNotEmpty()) {
|
if (songs != null && songs.isNotEmpty()) {
|
||||||
|
|
|
@ -38,52 +38,52 @@ object SearchQueryHelper {
|
||||||
|
|
||||||
var songs = ArrayList<Song>()
|
var songs = ArrayList<Song>()
|
||||||
if (artistName != null && albumName != null && titleName != null) {
|
if (artistName != null && albumName != null && titleName != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf(artistName.toLowerCase(), albumName.toLowerCase(), titleName.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf(artistName.toLowerCase(), albumName.toLowerCase(), titleName.toLowerCase())))
|
||||||
}
|
}
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
if (artistName != null && titleName != null) {
|
if (artistName != null && titleName != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + TITLE_SELECTION, arrayOf(artistName.toLowerCase(), titleName.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + TITLE_SELECTION, arrayOf(artistName.toLowerCase(), titleName.toLowerCase())))
|
||||||
}
|
}
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
if (albumName != null && titleName != null) {
|
if (albumName != null && titleName != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf(albumName.toLowerCase(), titleName.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION + AND + TITLE_SELECTION, arrayOf(albumName.toLowerCase(), titleName.toLowerCase())))
|
||||||
}
|
}
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
if (artistName != null) {
|
if (artistName != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, arrayOf(artistName.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, arrayOf(artistName.toLowerCase())))
|
||||||
}
|
}
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
if (albumName != null) {
|
if (albumName != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, arrayOf(albumName.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, arrayOf(albumName.toLowerCase())))
|
||||||
}
|
}
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
if (titleName != null) {
|
if (titleName != null) {
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, arrayOf(titleName.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, arrayOf(titleName.toLowerCase())))
|
||||||
}
|
}
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, arrayOf(query.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, arrayOf(query.toLowerCase())))
|
||||||
|
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, arrayOf(query.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, arrayOf(query.toLowerCase())))
|
||||||
if (!songs.isEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, arrayOf(query.toLowerCase()))).blockingFirst()
|
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, arrayOf(query.toLowerCase())))
|
||||||
return if (!songs.isEmpty()) {
|
return if (songs.isNotEmpty()) {
|
||||||
songs
|
songs
|
||||||
} else ArrayList()
|
} else ArrayList()
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,6 @@ object GenreMenuHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGenreSongs(activity: Activity, genre: Genre): ArrayList<Song> {
|
private fun getGenreSongs(activity: Activity, genre: Genre): ArrayList<Song> {
|
||||||
return GenreLoader.getSongs(activity, genre.id).blockingFirst()
|
return GenreLoader.getSongs(activity, genre.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,20 +77,18 @@ object PlaylistMenuHelper {
|
||||||
|
|
||||||
private fun getPlaylistSongs(activity: Activity,
|
private fun getPlaylistSongs(activity: Activity,
|
||||||
playlist: Playlist): ArrayList<Song> {
|
playlist: Playlist): ArrayList<Song> {
|
||||||
val songs: ArrayList<Song>
|
return if (playlist is AbsCustomPlaylist) {
|
||||||
if (playlist is AbsCustomPlaylist) {
|
playlist.getSongs(activity)
|
||||||
songs = playlist.getSongs(activity).blockingFirst()
|
|
||||||
} else {
|
} else {
|
||||||
songs = PlaylistSongsLoader.getPlaylistSongList(activity, playlist).blockingFirst()
|
PlaylistSongsLoader.getPlaylistSongList(activity, playlist)
|
||||||
}
|
}
|
||||||
return songs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SavePlaylistAsyncTask internal constructor(context: Context) : WeakContextAsyncTask<Playlist, String, String>(context) {
|
private class SavePlaylistAsyncTask internal constructor(context: Context) : WeakContextAsyncTask<Playlist, String, String>(context) {
|
||||||
|
|
||||||
override fun doInBackground(vararg params: Playlist): String {
|
override fun doInBackground(vararg params: Playlist): String {
|
||||||
return String.format(App.instance.applicationContext.getString(R.string
|
return String.format(App.instance.applicationContext.getString(R.string
|
||||||
.saved_playlist_to), PlaylistsUtil.savePlaylist(App.instance.applicationContext, params[0]).blockingFirst())
|
.saved_playlist_to), PlaylistsUtil.savePlaylist(App.instance.applicationContext, params[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostExecute(string: String) {
|
override fun onPostExecute(string: String) {
|
||||||
|
|
|
@ -21,89 +21,164 @@ import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 11/08/17.
|
* Created by hemanths on 11/08/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
open class AlbumLoader {
|
object AlbumLoader {
|
||||||
companion object {
|
fun getAllAlbumsFlowable(
|
||||||
fun getAllAlbums(context: Context): Observable<ArrayList<Album>> {
|
context: Context
|
||||||
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
): Observable<ArrayList<Album>> {
|
||||||
context, null, null,
|
val songs = SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||||
getSongLoaderSortOrder())
|
context, null, null,
|
||||||
)
|
getSongLoaderSortOrder())
|
||||||
|
)
|
||||||
|
|
||||||
return splitIntoAlbums(songs)
|
return splitIntoAlbumsFlowable(songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAlbums(context: Context,
|
fun getAlbumsFlowable(context: Context, query: String): Observable<ArrayList<Album>> {
|
||||||
query: String): Observable<ArrayList<Album>> {
|
val songs = SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||||
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
context,
|
||||||
context,
|
AudioColumns.ALBUM + " LIKE ?",
|
||||||
AudioColumns.ALBUM + " LIKE ?",
|
arrayOf("%$query%"),
|
||||||
arrayOf("%$query%"),
|
getSongLoaderSortOrder())
|
||||||
getSongLoaderSortOrder())
|
)
|
||||||
)
|
return splitIntoAlbumsFlowable(songs)
|
||||||
return splitIntoAlbums(songs)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun getAlbum(context: Context, albumId: Int): Observable<Album> {
|
fun getAlbums(
|
||||||
return Observable.create { e ->
|
context: Context,
|
||||||
val songs = SongLoader.getSongs(SongLoader
|
query: String
|
||||||
.makeSongCursor(context, AudioColumns.ALBUM_ID + "=?",
|
): ArrayList<Album> {
|
||||||
arrayOf(albumId.toString()), getSongLoaderSortOrder()))
|
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
songs.subscribe { songs1 ->
|
context,
|
||||||
e.onNext(Album(songs1))
|
AudioColumns.ALBUM + " LIKE ?",
|
||||||
e.onComplete()
|
arrayOf("%$query%"),
|
||||||
}
|
getSongLoaderSortOrder())
|
||||||
}
|
)
|
||||||
}
|
return splitIntoAlbums(songs)
|
||||||
|
}
|
||||||
|
|
||||||
fun splitIntoAlbums(
|
fun getAlbumFlowable(
|
||||||
songs: Observable<ArrayList<Song>>?): Observable<ArrayList<Album>> {
|
context: Context,
|
||||||
return Observable.create { e ->
|
albumId: Int
|
||||||
val albums = ArrayList<Album>()
|
): Observable<Album> {
|
||||||
songs?.subscribe { songs1 ->
|
return Observable.create { e ->
|
||||||
for (song in songs1) {
|
val songs = SongLoader.getSongsFlowable(SongLoader.makeSongCursor(context, AudioColumns.ALBUM_ID + "=?", arrayOf(albumId.toString()), getSongLoaderSortOrder()))
|
||||||
getOrCreateAlbum(albums, song.albumId).subscribe { album -> album.songs!!.add(song) }
|
songs.subscribe { songs1 ->
|
||||||
}
|
e.onNext(Album(songs1))
|
||||||
}
|
|
||||||
e.onNext(albums)
|
|
||||||
e.onComplete()
|
e.onComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fun splitIntoAlbums(songs: ArrayList<Song>?): ArrayList<Album> {
|
|
||||||
val albums = ArrayList<Album>()
|
fun getAlbum(
|
||||||
if (songs != null) {
|
context: Context,
|
||||||
for (song in songs) {
|
albumId: Int
|
||||||
getOrCreateAlbum(albums, song.albumId).subscribe { album -> album.songs!!.add(song) }
|
): Album {
|
||||||
}
|
val songs = SongLoader.getSongs(
|
||||||
}
|
SongLoader.makeSongCursor(
|
||||||
return albums
|
context,
|
||||||
}
|
AudioColumns.ALBUM_ID + "=?",
|
||||||
|
arrayOf(albumId.toString()),
|
||||||
private fun getOrCreateAlbum(albums: ArrayList<Album>, albumId: Int): Observable<Album> {
|
getSongLoaderSortOrder()))
|
||||||
return Observable.create { e ->
|
val album = Album(songs)
|
||||||
for (album in albums) {
|
sortSongsByTrackNumber(album)
|
||||||
if (!album.songs!!.isEmpty() && album.songs[0].albumId == albumId) {
|
return album
|
||||||
e.onNext(album)
|
}
|
||||||
e.onComplete()
|
|
||||||
return@create
|
fun splitIntoAlbumsFlowable(
|
||||||
}
|
songs: Observable<ArrayList<Song>>?
|
||||||
}
|
): Observable<ArrayList<Album>> {
|
||||||
val album = Album()
|
return Observable.create { e ->
|
||||||
albums.add(album)
|
val albums = ArrayList<Album>()
|
||||||
e.onNext(album)
|
songs?.subscribe { songs1 ->
|
||||||
e.onComplete()
|
for (song in songs1) {
|
||||||
}
|
getOrCreateAlbumFlowable(albums, song.albumId).subscribe { album ->
|
||||||
}
|
album.songs!!.add(song)
|
||||||
|
}
|
||||||
private fun getSongLoaderSortOrder(): String {
|
}
|
||||||
return PreferenceUtil.getInstance().albumSortOrder + ", " +
|
}
|
||||||
//PreferenceUtil.getInstance().getAlbumSongSortOrder() + "," +
|
for (album in albums) {
|
||||||
PreferenceUtil.getInstance().albumDetailSongSortOrder
|
sortSongsByTrackNumber(album)
|
||||||
}
|
}
|
||||||
|
e.onNext(albums)
|
||||||
|
e.onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllAlbums(
|
||||||
|
context: Context
|
||||||
|
): ArrayList<Album> {
|
||||||
|
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
|
context, null, null,
|
||||||
|
getSongLoaderSortOrder())
|
||||||
|
)
|
||||||
|
|
||||||
|
return splitIntoAlbums(songs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun splitIntoAlbums(
|
||||||
|
songs: ArrayList<Song>?
|
||||||
|
): ArrayList<Album> {
|
||||||
|
val albums = ArrayList<Album>()
|
||||||
|
if (songs != null) {
|
||||||
|
for (song in songs) {
|
||||||
|
getOrCreateAlbum(albums, song.albumId).songs?.add(song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (album in albums) {
|
||||||
|
sortSongsByTrackNumber(album)
|
||||||
|
}
|
||||||
|
return albums
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrCreateAlbumFlowable(
|
||||||
|
albums: ArrayList<Album>,
|
||||||
|
albumId: Int
|
||||||
|
): Observable<Album> {
|
||||||
|
return Observable.create { e ->
|
||||||
|
for (album in albums) {
|
||||||
|
if (!album.songs!!.isEmpty() && album.songs[0].albumId == albumId) {
|
||||||
|
e.onNext(album)
|
||||||
|
e.onComplete()
|
||||||
|
return@create
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val album = Album()
|
||||||
|
albums.add(album)
|
||||||
|
e.onNext(album)
|
||||||
|
e.onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOrCreateAlbum(
|
||||||
|
albums: ArrayList<Album>,
|
||||||
|
albumId: Int
|
||||||
|
): Album {
|
||||||
|
for (album in albums) {
|
||||||
|
if (album.songs!!.isNotEmpty() && album.songs[0].albumId == albumId) {
|
||||||
|
return album
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val album = Album()
|
||||||
|
albums.add(album)
|
||||||
|
return album
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortSongsByTrackNumber(
|
||||||
|
album: Album
|
||||||
|
) {
|
||||||
|
album.songs?.sortWith(Comparator { o1, o2 -> o1.trackNumber - o2.trackNumber })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSongLoaderSortOrder(): String {
|
||||||
|
return PreferenceUtil.getInstance().albumSortOrder + ", " +
|
||||||
|
//PreferenceUtil.getInstance().getAlbumSongSortOrder() + "," +
|
||||||
|
PreferenceUtil.getInstance().albumDetailSongSortOrder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import code.name.monkey.retromusic.model.Album
|
||||||
import code.name.monkey.retromusic.model.Artist
|
import code.name.monkey.retromusic.model.Artist
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
object ArtistLoader {
|
object ArtistLoader {
|
||||||
private fun getSongLoaderSortOrder(): String {
|
private fun getSongLoaderSortOrder(): String {
|
||||||
|
@ -30,36 +30,32 @@ object ArtistLoader {
|
||||||
PreferenceUtil.getInstance().artistDetailSongSortOrder
|
PreferenceUtil.getInstance().artistDetailSongSortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getArtist(context: Context, artistId: Int): Observable<Artist> {
|
fun getAllArtistsFlowable(
|
||||||
|
context: Context
|
||||||
|
): Observable<ArrayList<Artist>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
SongLoader.getSongs(SongLoader.makeSongCursor(context, AudioColumns.ARTIST_ID + "=?",
|
SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||||
arrayOf(artistId.toString()),
|
context, null, null,
|
||||||
getSongLoaderSortOrder()))
|
getSongLoaderSortOrder())
|
||||||
.subscribe { songs ->
|
).subscribe { songs ->
|
||||||
val artist = Artist(AlbumLoader.splitIntoAlbums(songs))
|
e.onNext(splitIntoArtists(AlbumLoader.splitIntoAlbums(songs)))
|
||||||
e.onNext(artist)
|
e.onComplete()
|
||||||
e.onComplete()
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllArtists(context: Context): Observable<ArrayList<Artist>> {
|
fun getAllArtists(context: Context): ArrayList<Artist> {
|
||||||
return Observable.create { e ->
|
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
SongLoader
|
context,
|
||||||
.getSongs(SongLoader.makeSongCursor(
|
null, null,
|
||||||
context, null, null,
|
getSongLoaderSortOrder())
|
||||||
getSongLoaderSortOrder())
|
)
|
||||||
).subscribe { songs ->
|
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))
|
||||||
e.onNext(splitIntoArtists(AlbumLoader.splitIntoAlbums(songs)))
|
|
||||||
e.onComplete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getArtists(context: Context, query: String): Observable<ArrayList<Artist>> {
|
fun getArtistsFlowable(context: Context, query: String): Observable<ArrayList<Artist>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
SongLoader.getSongs(SongLoader.makeSongCursor(
|
SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||||
context,
|
context,
|
||||||
AudioColumns.ARTIST + " LIKE ?",
|
AudioColumns.ARTIST + " LIKE ?",
|
||||||
arrayOf("%$query%"),
|
arrayOf("%$query%"),
|
||||||
|
@ -71,6 +67,16 @@ object ArtistLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getArtists(context: Context, query: String): ArrayList<Artist> {
|
||||||
|
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
|
context,
|
||||||
|
AudioColumns.ARTIST + " LIKE ?",
|
||||||
|
arrayOf("%$query%"),
|
||||||
|
getSongLoaderSortOrder())
|
||||||
|
)
|
||||||
|
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))
|
||||||
|
}
|
||||||
|
|
||||||
fun splitIntoArtists(albums: ArrayList<Album>?): ArrayList<Artist> {
|
fun splitIntoArtists(albums: ArrayList<Album>?): ArrayList<Artist> {
|
||||||
val artists = ArrayList<Artist>()
|
val artists = ArrayList<Artist>()
|
||||||
if (albums != null) {
|
if (albums != null) {
|
||||||
|
@ -83,7 +89,7 @@ object ArtistLoader {
|
||||||
|
|
||||||
private fun getOrCreateArtist(artists: ArrayList<Artist>, artistId: Int): Artist {
|
private fun getOrCreateArtist(artists: ArrayList<Artist>, artistId: Int): Artist {
|
||||||
for (artist in artists) {
|
for (artist in artists) {
|
||||||
if (!artist.albums!!.isEmpty() && !artist.albums[0].songs!!.isEmpty() && artist.albums[0].songs!![0].artistId == artistId) {
|
if (artist.albums!!.isNotEmpty() && artist.albums[0].songs!!.isNotEmpty() && artist.albums[0].songs!![0].artistId == artistId) {
|
||||||
return artist
|
return artist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,4 +112,27 @@ object ArtistLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getArtistFlowable(context: Context, artistId: Int): Observable<Artist> {
|
||||||
|
return Observable.create { e ->
|
||||||
|
SongLoader.getSongsFlowable(SongLoader.makeSongCursor(context, AudioColumns.ARTIST_ID + "=?",
|
||||||
|
arrayOf(artistId.toString()),
|
||||||
|
getSongLoaderSortOrder()))
|
||||||
|
.subscribe { songs ->
|
||||||
|
val artist = Artist(AlbumLoader.splitIntoAlbums(songs))
|
||||||
|
e.onNext(artist)
|
||||||
|
e.onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getArtist(context: Context, artistId: Int): Artist {
|
||||||
|
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||||
|
context,
|
||||||
|
AudioColumns.ARTIST_ID + "=?",
|
||||||
|
arrayOf(artistId.toString()),
|
||||||
|
getSongLoaderSortOrder())
|
||||||
|
)
|
||||||
|
return Artist(AlbumLoader.splitIntoAlbums(songs))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,27 @@ import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
object GenreLoader {
|
object GenreLoader {
|
||||||
|
|
||||||
fun getAllGenres(context: Context): Observable<ArrayList<Genre>> {
|
fun getAllGenresFlowable(context: Context): Observable<ArrayList<Genre>> {
|
||||||
|
return getGenresFromCursorFlowable(context, makeGenreCursor(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllGenres(context: Context): ArrayList<Genre> {
|
||||||
return getGenresFromCursor(context, makeGenreCursor(context))
|
return getGenresFromCursor(context, makeGenreCursor(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSongs(context: Context, genreId: Int): Observable<ArrayList<Song>> {
|
fun getSongsFlowable(context: Context, genreId: Int): Observable<ArrayList<Song>> {
|
||||||
|
// The genres table only stores songs that have a genre specified,
|
||||||
|
// so we need to get songs without a genre a different way.
|
||||||
|
return if (genreId == -1) {
|
||||||
|
getSongsWithNoGenreFlowable(context)
|
||||||
|
} else SongLoader.getSongsFlowable(makeGenreSongCursor(context, genreId))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSongs(context: Context, genreId: Int): ArrayList<Song> {
|
||||||
// The genres table only stores songs that have a genre specified,
|
// The genres table only stores songs that have a genre specified,
|
||||||
// so we need to get songs without a genre a different way.
|
// so we need to get songs without a genre a different way.
|
||||||
return if (genreId == -1) {
|
return if (genreId == -1) {
|
||||||
|
@ -45,12 +59,18 @@ object GenreLoader {
|
||||||
private fun getGenreFromCursor(context: Context, cursor: Cursor): Genre {
|
private fun getGenreFromCursor(context: Context, cursor: Cursor): Genre {
|
||||||
val id = cursor.getInt(0)
|
val id = cursor.getInt(0)
|
||||||
val name = cursor.getString(1)
|
val name = cursor.getString(1)
|
||||||
val songCount = getSongs(context, id).blockingFirst().size
|
val songCount = getSongs(context, id).size
|
||||||
return Genre(id, name, songCount)
|
return Genre(id, name, songCount)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSongsWithNoGenre(context: Context): Observable<ArrayList<Song>> {
|
private fun getSongsWithNoGenreFlowable(context: Context): Observable<ArrayList<Song>> {
|
||||||
|
val selection = BaseColumns._ID + " NOT IN " +
|
||||||
|
"(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
|
||||||
|
return SongLoader.getSongsFlowable(SongLoader.makeSongCursor(context, selection, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSongsWithNoGenre(context: Context): ArrayList<Song> {
|
||||||
val selection = BaseColumns._ID + " NOT IN " +
|
val selection = BaseColumns._ID + " NOT IN " +
|
||||||
"(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
|
"(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
|
||||||
return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null))
|
return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null))
|
||||||
|
@ -92,7 +112,7 @@ object GenreLoader {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGenresFromCursor(context: Context, cursor: Cursor?): Observable<ArrayList<Genre>> {
|
private fun getGenresFromCursorFlowable(context: Context, cursor: Cursor?): Observable<ArrayList<Genre>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
val genres = ArrayList<Genre>()
|
val genres = ArrayList<Genre>()
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
@ -120,6 +140,31 @@ object GenreLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getGenresFromCursor(context: Context, cursor: Cursor?): ArrayList<Genre> {
|
||||||
|
val genres = arrayListOf<Genre>()
|
||||||
|
if (cursor != null) {
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
val genre = getGenreFromCursor(context, cursor)
|
||||||
|
if (genre.songCount > 0) {
|
||||||
|
genres.add(genre)
|
||||||
|
} else {
|
||||||
|
// try to remove the empty genre from the media store
|
||||||
|
try {
|
||||||
|
context.contentResolver.delete(Genres.EXTERNAL_CONTENT_URI, Genres._ID + " == " + genre.id, null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
// nothing we can do then
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} while (cursor.moveToNext())
|
||||||
|
}
|
||||||
|
cursor.close()
|
||||||
|
}
|
||||||
|
return genres
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun makeGenreCursor(context: Context): Cursor? {
|
private fun makeGenreCursor(context: Context): Cursor? {
|
||||||
val projection = arrayOf(Genres._ID, Genres.NAME)
|
val projection = arrayOf(Genres._ID, Genres.NAME)
|
||||||
|
|
|
@ -25,47 +25,5 @@ import io.reactivex.Observable
|
||||||
|
|
||||||
object HomeLoader {
|
object HomeLoader {
|
||||||
|
|
||||||
fun getRecentAndTopThings(context: Context): Observable<ArrayList<AbsSmartPlaylist>> {
|
|
||||||
val objects = ArrayList<AbsSmartPlaylist>()
|
|
||||||
|
|
||||||
return Observable.create { e ->
|
|
||||||
|
|
||||||
HistoryPlaylist(context).getSongs(context).subscribe { songs ->
|
|
||||||
if (!songs.isEmpty()) {
|
|
||||||
objects.add(HistoryPlaylist(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LastAddedPlaylist(context).getSongs(context).subscribe { songs ->
|
|
||||||
if (!songs.isEmpty()) {
|
|
||||||
objects.add(LastAddedPlaylist(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MyTopTracksPlaylist(context).getSongs(context).subscribe { songs ->
|
|
||||||
if (!songs.isEmpty()) {
|
|
||||||
objects.add(MyTopTracksPlaylist(context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.onNext(objects)
|
|
||||||
e.onComplete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getHomeLoader(context: Context): Observable<ArrayList<Playlist>> {
|
|
||||||
val playlists = ArrayList<Playlist>()
|
|
||||||
PlaylistLoader.getAllPlaylists(context)
|
|
||||||
.subscribe { playlists1 ->
|
|
||||||
if (playlists1.size > 0) {
|
|
||||||
for (playlist in playlists1) {
|
|
||||||
PlaylistSongsLoader.getPlaylistSongList(context, playlist)
|
|
||||||
.subscribe { songs ->
|
|
||||||
if (songs.size > 0) {
|
|
||||||
playlists.add(playlist)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Observable.just(playlists)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ import code.name.monkey.retromusic.model.Artist
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.annotations.NonNull
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 16/08/17.
|
* Created by hemanths on 16/08/17.
|
||||||
|
@ -31,12 +31,16 @@ import java.util.*
|
||||||
|
|
||||||
object LastAddedSongsLoader {
|
object LastAddedSongsLoader {
|
||||||
|
|
||||||
@NonNull
|
|
||||||
fun getLastAddedSongs(@NonNull context: Context): Observable<ArrayList<Song>> {
|
fun getLastAddedSongsFlowable(context: Context): Observable<ArrayList<Song>> {
|
||||||
|
return SongLoader.getSongsFlowable(makeLastAddedCursor(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastAddedSongs(context: Context): ArrayList<Song> {
|
||||||
return SongLoader.getSongs(makeLastAddedCursor(context))
|
return SongLoader.getSongs(makeLastAddedCursor(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeLastAddedCursor(@NonNull context: Context): Cursor? {
|
private fun makeLastAddedCursor(context: Context): Cursor? {
|
||||||
val cutoff = PreferenceUtil.getInstance().lastAddedCutoff
|
val cutoff = PreferenceUtil.getInstance().lastAddedCutoff
|
||||||
|
|
||||||
return SongLoader.makeSongCursor(
|
return SongLoader.makeSongCursor(
|
||||||
|
@ -46,13 +50,22 @@ object LastAddedSongsLoader {
|
||||||
MediaStore.Audio.Media.DATE_ADDED + " DESC")
|
MediaStore.Audio.Media.DATE_ADDED + " DESC")
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
fun getLastAddedAlbums(@NonNull context: Context): Observable<ArrayList<Album>> {
|
fun getLastAddedAlbumsFlowable(context: Context): Observable<ArrayList<Album>> {
|
||||||
|
return AlbumLoader.splitIntoAlbumsFlowable(getLastAddedSongsFlowable(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getLastAddedAlbums(context: Context): ArrayList<Album> {
|
||||||
return AlbumLoader.splitIntoAlbums(getLastAddedSongs(context))
|
return AlbumLoader.splitIntoAlbums(getLastAddedSongs(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
fun getLastAddedArtists(@NonNull context: Context): Observable<ArrayList<Artist>> {
|
fun getLastAddedArtistsFlowable(context: Context): Observable<ArrayList<Artist>> {
|
||||||
|
return ArtistLoader.splitIntoArtists(getLastAddedAlbumsFlowable(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastAddedArtists(context: Context): ArrayList<Artist> {
|
||||||
return ArtistLoader.splitIntoArtists(getLastAddedAlbums(context))
|
return ArtistLoader.splitIntoArtists(getLastAddedAlbums(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,31 +19,20 @@ import android.database.Cursor
|
||||||
import android.provider.BaseColumns
|
import android.provider.BaseColumns
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.MediaStore.Audio.PlaylistsColumns
|
import android.provider.MediaStore.Audio.PlaylistsColumns
|
||||||
import code.name.monkey.retromusic.R
|
|
||||||
import code.name.monkey.retromusic.model.Playlist
|
import code.name.monkey.retromusic.model.Playlist
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 16/08/17.
|
* Created by hemanths on 16/08/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
object PlaylistLoader {
|
object PlaylistLoader {
|
||||||
private fun makePlaylistCursor(context: Context, selection: String?, values: Array<String>?): Cursor? {
|
|
||||||
return try {
|
|
||||||
context.contentResolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
|
|
||||||
arrayOf(
|
|
||||||
/* 0 */
|
|
||||||
BaseColumns._ID,
|
|
||||||
/* 1 */
|
|
||||||
PlaylistsColumns.NAME), selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER)
|
|
||||||
|
|
||||||
} catch (e: SecurityException) {
|
private fun getPlaylistFlowable(
|
||||||
null
|
cursor: Cursor?
|
||||||
}
|
): Observable<Playlist> {
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPlaylist(cursor: Cursor?): Observable<Playlist> {
|
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
var playlist = Playlist()
|
var playlist = Playlist()
|
||||||
|
|
||||||
|
@ -55,11 +44,35 @@ object PlaylistLoader {
|
||||||
e.onNext(playlist)
|
e.onNext(playlist)
|
||||||
e.onComplete()
|
e.onComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaylist(context: Context, playlistName: String): Observable<Playlist> {
|
fun getPlaylist(
|
||||||
|
cursor: Cursor?
|
||||||
|
): Playlist {
|
||||||
|
var playlist = Playlist()
|
||||||
|
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
playlist = getPlaylistFromCursorImpl(cursor)
|
||||||
|
}
|
||||||
|
cursor?.close()
|
||||||
|
return playlist
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlaylistFlowable(
|
||||||
|
context: Context,
|
||||||
|
playlistName: String
|
||||||
|
): Observable<Playlist> {
|
||||||
|
return getPlaylistFlowable(makePlaylistCursor(
|
||||||
|
context,
|
||||||
|
PlaylistsColumns.NAME + "=?",
|
||||||
|
arrayOf(playlistName)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlaylist(
|
||||||
|
context: Context,
|
||||||
|
playlistName: String
|
||||||
|
): Playlist {
|
||||||
return getPlaylist(makePlaylistCursor(
|
return getPlaylist(makePlaylistCursor(
|
||||||
context,
|
context,
|
||||||
PlaylistsColumns.NAME + "=?",
|
PlaylistsColumns.NAME + "=?",
|
||||||
|
@ -67,23 +80,31 @@ object PlaylistLoader {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaylist(context: Context, playlistId: Int): Observable<Playlist> {
|
fun getPlaylistFlowable(
|
||||||
return getPlaylist(makePlaylistCursor(
|
context: Context,
|
||||||
|
playlistId: Int
|
||||||
|
): Observable<Playlist> {
|
||||||
|
return getPlaylistFlowable(makePlaylistCursor(
|
||||||
context,
|
context,
|
||||||
BaseColumns._ID + "=?",
|
BaseColumns._ID + "=?",
|
||||||
arrayOf(playlistId.toString())
|
arrayOf(playlistId.toString())
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlaylistFromCursorImpl(cursor: Cursor): Playlist {
|
fun getAllPlaylistsFlowoable(
|
||||||
|
context: Context
|
||||||
val id = cursor.getInt(0)
|
): Observable<ArrayList<Playlist>> {
|
||||||
val name = cursor.getString(1)
|
return getAllPlaylistsFlowable(makePlaylistCursor(context, null, null))
|
||||||
return Playlist(id, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFavoritePlaylistFlowable(context: Context): Observable<ArrayList<Playlist>> {
|
||||||
|
return getAllPlaylistsFlowable(makePlaylistCursor(
|
||||||
|
context,
|
||||||
|
PlaylistsColumns.NAME + "=?",
|
||||||
|
arrayOf(context.getString(code.name.monkey.retromusic.R.string.favorites))))
|
||||||
|
}
|
||||||
|
|
||||||
private fun getAllPlaylists(cursor: Cursor?): Observable<ArrayList<Playlist>> {
|
private fun getAllPlaylistsFlowable(cursor: Cursor?): Observable<ArrayList<Playlist>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
val playlists = ArrayList<Playlist>()
|
val playlists = ArrayList<Playlist>()
|
||||||
|
|
||||||
|
@ -99,15 +120,27 @@ object PlaylistLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllPlaylists(context: Context): Observable<ArrayList<Playlist>> {
|
fun getAllPlaylists(context: Context): ArrayList<Playlist> {
|
||||||
return getAllPlaylists(makePlaylistCursor(context, null, null))
|
return getAllPlaylists(makePlaylistCursor(context, null, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFavoritePlaylist(context: Context): Observable<ArrayList<Playlist>> {
|
fun getFavoritePlaylist(context: Context): ArrayList<Playlist> {
|
||||||
return getAllPlaylists(makePlaylistCursor(
|
return getAllPlaylists(makePlaylistCursor(
|
||||||
context,
|
context,
|
||||||
PlaylistsColumns.NAME + "=?",
|
PlaylistsColumns.NAME + "=?",
|
||||||
arrayOf(context.getString(R.string.favorites))))
|
arrayOf(context.getString(code.name.monkey.retromusic.R.string.favorites))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllPlaylists(cursor: Cursor?): ArrayList<Playlist> {
|
||||||
|
val playlists = ArrayList<Playlist>()
|
||||||
|
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
playlists.add(getPlaylistFromCursorImpl(cursor))
|
||||||
|
} while (cursor.moveToNext())
|
||||||
|
}
|
||||||
|
cursor?.close()
|
||||||
|
return playlists
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deletePlaylists(context: Context, playlistId: Long) {
|
fun deletePlaylists(context: Context, playlistId: Long) {
|
||||||
|
@ -118,4 +151,42 @@ object PlaylistLoader {
|
||||||
localStringBuilder.append(")")
|
localStringBuilder.append(")")
|
||||||
context.contentResolver.delete(localUri, localStringBuilder.toString(), null)
|
context.contentResolver.delete(localUri, localStringBuilder.toString(), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun makePlaylistCursor(
|
||||||
|
context: Context,
|
||||||
|
selection: String?,
|
||||||
|
values: Array<String>?
|
||||||
|
): Cursor? {
|
||||||
|
try {
|
||||||
|
return context.contentResolver.query(
|
||||||
|
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
|
||||||
|
arrayOf(BaseColumns._ID, /* 0 */
|
||||||
|
PlaylistsColumns.NAME), /* 1 */
|
||||||
|
selection,
|
||||||
|
values,
|
||||||
|
MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlaylist(
|
||||||
|
context: Context,
|
||||||
|
playlistId: Int
|
||||||
|
): Playlist {
|
||||||
|
return getPlaylist(makePlaylistCursor(
|
||||||
|
context,
|
||||||
|
BaseColumns._ID + "=?",
|
||||||
|
arrayOf(playlistId.toString())
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPlaylistFromCursorImpl(
|
||||||
|
cursor: Cursor
|
||||||
|
): Playlist {
|
||||||
|
val id = cursor.getInt(0)
|
||||||
|
val name = cursor.getString(1)
|
||||||
|
return Playlist(id, name)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,23 +24,33 @@ import code.name.monkey.retromusic.model.Playlist
|
||||||
import code.name.monkey.retromusic.model.PlaylistSong
|
import code.name.monkey.retromusic.model.PlaylistSong
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.annotations.NonNull
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 16/08/17.
|
* Created by hemanths on 16/08/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
object PlaylistSongsLoader {
|
object PlaylistSongsLoader {
|
||||||
|
|
||||||
@NonNull
|
fun getPlaylistSongListFlowable(
|
||||||
fun getPlaylistSongList(@NonNull context: Context, playlist: Playlist): Observable<ArrayList<Song>> {
|
context: Context,
|
||||||
|
playlist: Playlist
|
||||||
|
): Observable<ArrayList<Song>> {
|
||||||
|
return (playlist as? AbsCustomPlaylist)?.getSongsFlowable(context)
|
||||||
|
?: getPlaylistSongListFlowable(context, playlist.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlaylistSongList(
|
||||||
|
context: Context,
|
||||||
|
playlist: Playlist
|
||||||
|
): ArrayList<Song> {
|
||||||
return (playlist as? AbsCustomPlaylist)?.getSongs(context)
|
return (playlist as? AbsCustomPlaylist)?.getSongs(context)
|
||||||
?: getPlaylistSongList(context, playlist.id)
|
?: getPlaylistSongList(context, playlist.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
fun getPlaylistSongList(@NonNull context: Context, playlistId: Int): Observable<ArrayList<Song>> {
|
fun getPlaylistSongListFlowable(context: Context, playlistId: Int): Observable<ArrayList<Song>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
val songs = ArrayList<Song>()
|
val songs = ArrayList<Song>()
|
||||||
val cursor = makePlaylistSongCursor(context, playlistId)
|
val cursor = makePlaylistSongCursor(context, playlistId)
|
||||||
|
@ -56,8 +66,21 @@ object PlaylistSongsLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList<Song> {
|
||||||
private fun getPlaylistSongFromCursorImpl(@NonNull cursor: Cursor, playlistId: Int): PlaylistSong {
|
val songs = arrayListOf<Song>()
|
||||||
|
val cursor = makePlaylistSongCursor(context, playlistId)
|
||||||
|
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
songs.add(getPlaylistSongFromCursorImpl(cursor, playlistId))
|
||||||
|
} while (cursor.moveToNext())
|
||||||
|
}
|
||||||
|
cursor?.close()
|
||||||
|
return songs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getPlaylistSongFromCursorImpl(cursor: Cursor, playlistId: Int): PlaylistSong {
|
||||||
val id = cursor.getInt(0)
|
val id = cursor.getInt(0)
|
||||||
val title = cursor.getString(1)
|
val title = cursor.getString(1)
|
||||||
val trackNumber = cursor.getInt(2)
|
val trackNumber = cursor.getInt(2)
|
||||||
|
@ -75,7 +98,7 @@ object PlaylistSongsLoader {
|
||||||
return PlaylistSong(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName, playlistId, idInPlaylist, composer)
|
return PlaylistSong(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName, playlistId, idInPlaylist, composer)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makePlaylistSongCursor(@NonNull context: Context, playlistId: Int): Cursor? {
|
private fun makePlaylistSongCursor(context: Context, playlistId: Int): Cursor? {
|
||||||
try {
|
try {
|
||||||
return context.contentResolver.query(
|
return context.contentResolver.query(
|
||||||
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()),
|
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()),
|
||||||
|
|
|
@ -15,42 +15,33 @@
|
||||||
package code.name.monkey.retromusic.loaders
|
package code.name.monkey.retromusic.loaders
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.TextUtils
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object SearchLoader {
|
object SearchLoader {
|
||||||
|
fun searchAll(context: Context, query: String?): MutableList<Any> {
|
||||||
fun searchAll(context: Context, query: String?): Observable<ArrayList<Any>> {
|
val results = mutableListOf<Any>()
|
||||||
val results = ArrayList<Any>()
|
query?.let {
|
||||||
return Observable.create { e ->
|
val songs = SongLoader.getSongs(context, it)
|
||||||
if (!TextUtils.isEmpty(query)) {
|
if (songs.isNotEmpty()) {
|
||||||
SongLoader.getSongs(context, query!!)
|
results.add(context.resources.getString(R.string.songs))
|
||||||
.subscribe { songs ->
|
results.addAll(songs)
|
||||||
if (!songs.isEmpty()) {
|
}
|
||||||
results.add(context.resources.getString(R.string.songs))
|
|
||||||
results.addAll(songs)
|
val artists = ArtistLoader.getArtists(context, it)
|
||||||
}
|
if (artists.isNotEmpty()) {
|
||||||
}
|
results.add(context.resources.getString(R.string.artists))
|
||||||
|
results.addAll(artists)
|
||||||
ArtistLoader.getArtists(context, query)
|
}
|
||||||
.subscribe { artists ->
|
|
||||||
if (!artists.isEmpty()) {
|
val albums = AlbumLoader.getAlbums(context, it)
|
||||||
results.add(context.resources.getString(R.string.artists))
|
if (albums.isNotEmpty()) {
|
||||||
results.addAll(artists)
|
results.add(context.resources.getString(R.string.albums))
|
||||||
}
|
results.addAll(albums)
|
||||||
}
|
|
||||||
AlbumLoader.getAlbums(context, query)
|
|
||||||
.subscribe { albums ->
|
|
||||||
if (!albums.isEmpty()) {
|
|
||||||
results.add(context.resources.getString(R.string.albums))
|
|
||||||
results.addAll(albums)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
e.onNext(results)
|
|
||||||
e.onComplete()
|
|
||||||
}
|
}
|
||||||
|
return results
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,24 +27,29 @@ import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 10/08/17.
|
* Created by hemanths on 10/08/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
object SongLoader {
|
object SongLoader {
|
||||||
|
fun getAllSongsFlowable(
|
||||||
|
context: Context
|
||||||
|
): Observable<ArrayList<Song>> {
|
||||||
|
val cursor = makeSongCursor(context, null, null)
|
||||||
|
return getSongsFlowable(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllSongs(context: Context): Observable<ArrayList<Song>> {
|
fun getAllSongs(
|
||||||
|
context: Context
|
||||||
|
): ArrayList<Song> {
|
||||||
val cursor = makeSongCursor(context, null, null)
|
val cursor = makeSongCursor(context, null, null)
|
||||||
return getSongs(cursor)
|
return getSongs(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSongs(context: Context, query: String): Observable<ArrayList<Song>> {
|
fun getSongsFlowable(
|
||||||
val cursor = makeSongCursor(context, AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%"))
|
cursor: Cursor?
|
||||||
return getSongs(cursor)
|
): Observable<ArrayList<Song>> {
|
||||||
}
|
|
||||||
|
|
||||||
fun getSongs(cursor: Cursor?): Observable<ArrayList<Song>> {
|
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
val songs = ArrayList<Song>()
|
val songs = ArrayList<Song>()
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
@ -59,7 +64,96 @@ object SongLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSongFromCursorImpl(cursor: Cursor): Song {
|
fun getSongs(
|
||||||
|
cursor: Cursor?
|
||||||
|
): ArrayList<Song> {
|
||||||
|
val songs = arrayListOf<Song>()
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
songs.add(getSongFromCursorImpl(cursor))
|
||||||
|
} while (cursor.moveToNext())
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor?.close()
|
||||||
|
return songs
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSongsFlowable(
|
||||||
|
context: Context,
|
||||||
|
query: String
|
||||||
|
): Observable<ArrayList<Song>> {
|
||||||
|
val cursor = makeSongCursor(context, AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%"))
|
||||||
|
return getSongsFlowable(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSongs(
|
||||||
|
context: Context,
|
||||||
|
query: String
|
||||||
|
): ArrayList<Song> {
|
||||||
|
val cursor = makeSongCursor(context, AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%"))
|
||||||
|
return getSongs(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getSongFlowable(
|
||||||
|
cursor: Cursor?
|
||||||
|
): Observable<Song> {
|
||||||
|
return Observable.create { e ->
|
||||||
|
val song: Song = if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
getSongFromCursorImpl(cursor)
|
||||||
|
} else {
|
||||||
|
Song.emptySong
|
||||||
|
}
|
||||||
|
cursor?.close()
|
||||||
|
e.onNext(song)
|
||||||
|
e.onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSong(
|
||||||
|
cursor: Cursor?
|
||||||
|
): Song {
|
||||||
|
val song: Song
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
song = getSongFromCursorImpl(cursor)
|
||||||
|
} else {
|
||||||
|
song = Song.emptySong
|
||||||
|
}
|
||||||
|
cursor?.close()
|
||||||
|
return song
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSongFlowable(
|
||||||
|
context: Context,
|
||||||
|
queryId: Int
|
||||||
|
): Observable<Song> {
|
||||||
|
val cursor = makeSongCursor(context, AudioColumns._ID + "=?",
|
||||||
|
arrayOf(queryId.toString()))
|
||||||
|
return getSongFlowable(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSong(context: Context, queryId: Int): Song {
|
||||||
|
val cursor = makeSongCursor(context, AudioColumns._ID + "=?", arrayOf(queryId.toString()))
|
||||||
|
return getSong(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun suggestSongs(
|
||||||
|
context: Context
|
||||||
|
): Observable<ArrayList<Song>> {
|
||||||
|
return SongLoader.getAllSongsFlowable(context)
|
||||||
|
.flatMap {
|
||||||
|
val list = ArrayList<Song>()
|
||||||
|
ShuffleHelper.makeShuffleList(it, -1)
|
||||||
|
if (it.size >= 7) {
|
||||||
|
list.addAll(it.subList(0, 7))
|
||||||
|
}
|
||||||
|
return@flatMap Observable.just(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSongFromCursorImpl(
|
||||||
|
cursor: Cursor
|
||||||
|
): Song {
|
||||||
val id = cursor.getInt(0)
|
val id = cursor.getInt(0)
|
||||||
val title = cursor.getString(1)
|
val title = cursor.getString(1)
|
||||||
val trackNumber = cursor.getInt(2)
|
val trackNumber = cursor.getInt(2)
|
||||||
|
@ -78,7 +172,12 @@ object SongLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun makeSongCursor(context: Context, selection: String?, selectionValues: Array<String>?, sortOrder: String = PreferenceUtil.getInstance().songSortOrder): Cursor? {
|
fun makeSongCursor(
|
||||||
|
context: Context,
|
||||||
|
selection: String?,
|
||||||
|
selectionValues: Array<String>?,
|
||||||
|
sortOrder: String = PreferenceUtil.getInstance().songSortOrder
|
||||||
|
): Cursor? {
|
||||||
var selectionFinal = selection
|
var selectionFinal = selection
|
||||||
var selectionValuesFinal = selectionValues
|
var selectionValuesFinal = selectionValues
|
||||||
selectionFinal = if (selection != null && selection.trim { it <= ' ' } != "") {
|
selectionFinal = if (selection != null && selection.trim { it <= ' ' } != "") {
|
||||||
|
@ -103,7 +202,10 @@ object SongLoader {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateBlacklistSelection(selection: String?, pathCount: Int): String {
|
private fun generateBlacklistSelection(
|
||||||
|
selection: String?,
|
||||||
|
pathCount: Int
|
||||||
|
): String {
|
||||||
val newSelection = StringBuilder(
|
val newSelection = StringBuilder(
|
||||||
if (selection != null && selection.trim { it <= ' ' } != "") "$selection AND " else "")
|
if (selection != null && selection.trim { it <= ' ' } != "") "$selection AND " else "")
|
||||||
newSelection.append(AudioColumns.DATA + " NOT LIKE ?")
|
newSelection.append(AudioColumns.DATA + " NOT LIKE ?")
|
||||||
|
@ -113,8 +215,10 @@ object SongLoader {
|
||||||
return newSelection.toString()
|
return newSelection.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addBlacklistSelectionValues(selectionValues: Array<String>?,
|
private fun addBlacklistSelectionValues(
|
||||||
paths: ArrayList<String>): Array<String>? {
|
selectionValues: Array<String>?,
|
||||||
|
paths: ArrayList<String>
|
||||||
|
): Array<String>? {
|
||||||
var selectionValuesFinal = selectionValues
|
var selectionValuesFinal = selectionValues
|
||||||
if (selectionValuesFinal == null) {
|
if (selectionValuesFinal == null) {
|
||||||
selectionValuesFinal = emptyArray()
|
selectionValuesFinal = emptyArray()
|
||||||
|
@ -128,35 +232,4 @@ object SongLoader {
|
||||||
}
|
}
|
||||||
return newSelectionValues
|
return newSelectionValues
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSong(cursor: Cursor?): Observable<Song> {
|
|
||||||
return Observable.create { e ->
|
|
||||||
val song: Song = if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
getSongFromCursorImpl(cursor)
|
|
||||||
} else {
|
|
||||||
Song.emptySong
|
|
||||||
}
|
|
||||||
cursor?.close()
|
|
||||||
e.onNext(song)
|
|
||||||
e.onComplete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSong(context: Context, queryId: Int): Observable<Song> {
|
|
||||||
val cursor = makeSongCursor(context, AudioColumns._ID + "=?",
|
|
||||||
arrayOf(queryId.toString()))
|
|
||||||
return getSong(cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun suggestSongs(context: Context): Observable<ArrayList<Song>> {
|
|
||||||
return SongLoader.getAllSongs(context)
|
|
||||||
.flatMap {
|
|
||||||
val list = ArrayList<Song>()
|
|
||||||
ShuffleHelper.makeShuffleList(it, -1)
|
|
||||||
if (it.size >= 7) {
|
|
||||||
list.addAll(it.subList(0, 7))
|
|
||||||
}
|
|
||||||
return@flatMap Observable.just(list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,11 +32,19 @@ import java.util.*
|
||||||
|
|
||||||
object TopAndRecentlyPlayedTracksLoader {
|
object TopAndRecentlyPlayedTracksLoader {
|
||||||
|
|
||||||
fun getRecentlyPlayedTracks(context: Context): Observable<ArrayList<Song>> {
|
fun getRecentlyPlayedTracksFlowable(context: Context): Observable<ArrayList<Song>> {
|
||||||
|
return SongLoader.getSongsFlowable(makeRecentTracksCursorAndClearUpDatabase(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRecentlyPlayedTracks(context: Context): ArrayList<Song> {
|
||||||
return SongLoader.getSongs(makeRecentTracksCursorAndClearUpDatabase(context))
|
return SongLoader.getSongs(makeRecentTracksCursorAndClearUpDatabase(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTopTracks(context: Context): Observable<ArrayList<Song>> {
|
fun getTopTracksFlowable(context: Context): Observable<ArrayList<Song>> {
|
||||||
|
return SongLoader.getSongsFlowable(makeTopTracksCursorAndClearUpDatabase(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTopTracks(context: Context): ArrayList<Song> {
|
||||||
return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context))
|
return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,9 +138,11 @@ object TopAndRecentlyPlayedTracksLoader {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTopAlbums(context: Context): Observable<ArrayList<Album>> {
|
fun getTopAlbumsFlowable(
|
||||||
|
context: Context
|
||||||
|
): Observable<ArrayList<Album>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
getTopTracks(context).subscribe { songs ->
|
getTopTracksFlowable(context).subscribe { songs ->
|
||||||
if (songs.size > 0) {
|
if (songs.size > 0) {
|
||||||
e.onNext(AlbumLoader.splitIntoAlbums(songs))
|
e.onNext(AlbumLoader.splitIntoAlbums(songs))
|
||||||
}
|
}
|
||||||
|
@ -141,9 +151,16 @@ object TopAndRecentlyPlayedTracksLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTopArtists(context: Context): Observable<ArrayList<Artist>> {
|
fun getTopAlbums(
|
||||||
|
context: Context
|
||||||
|
): ArrayList<Album> {
|
||||||
|
arrayListOf<Album>()
|
||||||
|
return AlbumLoader.splitIntoAlbums(getTopTracks(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTopArtistsFlowable(context: Context): Observable<ArrayList<Artist>> {
|
||||||
return Observable.create { e ->
|
return Observable.create { e ->
|
||||||
getTopAlbums(context).subscribe { albums ->
|
getTopAlbumsFlowable(context).subscribe { albums ->
|
||||||
if (albums.size > 0) {
|
if (albums.size > 0) {
|
||||||
e.onNext(ArtistLoader.splitIntoArtists(albums))
|
e.onNext(ArtistLoader.splitIntoArtists(albums))
|
||||||
}
|
}
|
||||||
|
@ -151,4 +168,8 @@ object TopAndRecentlyPlayedTracksLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTopArtists(context: Context): ArrayList<Artist> {
|
||||||
|
return ArtistLoader.splitIntoArtists(getTopAlbums(context))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import android.os.Parcel;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
@ -40,5 +42,8 @@ public abstract class AbsCustomPlaylist extends Playlist {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract Observable<ArrayList<Song>> getSongs(Context context);
|
public abstract Observable<ArrayList<Song>> getSongsFlowable(@NotNull Context context);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract ArrayList<Song> getSongs(@NotNull Context context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,28 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.model;
|
package code.name.monkey.retromusic.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
|
||||||
|
|
||||||
public class Playlist implements Parcelable {
|
public class Playlist implements Parcelable {
|
||||||
|
public static final Creator<Playlist> CREATOR = new Creator<Playlist>() {
|
||||||
|
public Playlist createFromParcel(Parcel source) {
|
||||||
|
return new Playlist(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Playlist[] newArray(int size) {
|
||||||
|
return new Playlist[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
public final int id;
|
public final int id;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
|
@ -32,6 +49,23 @@ public class Playlist implements Parcelable {
|
||||||
this.name = "";
|
this.name = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Playlist(Parcel in) {
|
||||||
|
this.id = in.readInt();
|
||||||
|
this.name = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Observable<ArrayList<Song>> getSongsFlowable(@NonNull Context context) {
|
||||||
|
// this default implementation covers static playlists
|
||||||
|
return PlaylistSongsLoader.INSTANCE.getPlaylistSongListFlowable(context, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ArrayList<Song> getSongs(@NonNull Context context) {
|
||||||
|
// this default implementation covers static playlists
|
||||||
|
return PlaylistSongsLoader.INSTANCE.getPlaylistSongList(context, id);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -59,7 +93,6 @@ public class Playlist implements Parcelable {
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -71,20 +104,5 @@ public class Playlist implements Parcelable {
|
||||||
dest.writeString(this.name);
|
dest.writeString(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Playlist(Parcel in) {
|
|
||||||
this.id = in.readInt();
|
|
||||||
this.name = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<Playlist> CREATOR = new Creator<Playlist>() {
|
|
||||||
public Playlist createFromParcel(Parcel source) {
|
|
||||||
return new Playlist(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Playlist[] newArray(int size) {
|
|
||||||
return new Playlist[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
@ -51,7 +54,13 @@ public class HistoryPlaylist extends AbsSmartPlaylist {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Observable<ArrayList<Song>> getSongs(@NonNull Context context) {
|
public Observable<ArrayList<Song>> getSongsFlowable(@NotNull @NonNull Context context) {
|
||||||
|
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getRecentlyPlayedTracksFlowable(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ArrayList<Song> getSongs(@NotNull @NonNull Context context) {
|
||||||
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getRecentlyPlayedTracks(context);
|
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getRecentlyPlayedTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.loaders.LastAddedSongsLoader;
|
import code.name.monkey.retromusic.loaders.LastAddedSongsLoader;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
@ -48,7 +51,13 @@ public class LastAddedPlaylist extends AbsSmartPlaylist {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Observable<ArrayList<Song>> getSongs(@NonNull Context context) {
|
public Observable<ArrayList<Song>> getSongsFlowable(@NotNull @NonNull Context context) {
|
||||||
|
return LastAddedSongsLoader.INSTANCE.getLastAddedSongsFlowable(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ArrayList<Song> getSongs(@NotNull @NonNull Context context) {
|
||||||
return LastAddedSongsLoader.INSTANCE.getLastAddedSongs(context);
|
return LastAddedSongsLoader.INSTANCE.getLastAddedSongs(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
@ -51,7 +54,13 @@ public class MyTopTracksPlaylist extends AbsSmartPlaylist {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Observable<ArrayList<Song>> getSongs(@NonNull Context context) {
|
public Observable<ArrayList<Song>> getSongsFlowable(@NotNull @NonNull Context context) {
|
||||||
|
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getTopTracksFlowable(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ArrayList<Song> getSongs(@NotNull @NonNull Context context) {
|
||||||
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getTopTracks(context);
|
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getTopTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
@ -47,7 +50,13 @@ public class ShuffleAllPlaylist extends AbsSmartPlaylist {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Observable<ArrayList<Song>> getSongs(@NonNull Context context) {
|
public Observable<ArrayList<Song>> getSongsFlowable(@NotNull @NonNull Context context) {
|
||||||
|
return SongLoader.INSTANCE.getAllSongsFlowable(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ArrayList<Song> getSongs(@NotNull Context context) {
|
||||||
return SongLoader.INSTANCE.getAllSongs(context);
|
return SongLoader.INSTANCE.getAllSongs(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface SearchContract {
|
interface SearchContract {
|
||||||
interface SearchView : BaseView<ArrayList<Any>>
|
interface SearchView : BaseView<MutableList<Any>>
|
||||||
|
|
||||||
interface SearchPresenter : BasePresenter<SearchView> {
|
interface SearchPresenter : BasePresenter<SearchView> {
|
||||||
fun search(query: String?)
|
fun search(query: String?)
|
||||||
|
|
|
@ -34,9 +34,7 @@ class AlbumDetailsPresenter(private val view: AlbumDetailsContract.AlbumDetailsV
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadAlbumSongs(albumId: Int) {
|
override fun loadAlbumSongs(albumId: Int) {
|
||||||
disposable.add(repository.getAlbum(albumId)
|
disposable.add(repository.getAlbumFlowable(albumId)
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showAlbum(it) },
|
.subscribe({ this.showAlbum(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -39,9 +39,7 @@ class AlbumPresenter(private val view: AlbumContract.AlbumView) : Presenter(), A
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadAlbums() {
|
override fun loadAlbums() {
|
||||||
disposable.add(repository.allAlbums
|
disposable.add(repository.allAlbumsFlowable
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showList(it) },
|
.subscribe({ this.showList(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -37,9 +37,7 @@ class ArtistDetailsPresenter(private val view: ArtistDetailContract.ArtistsDetai
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadArtistById() {
|
override fun loadArtistById() {
|
||||||
disposable.add(repository.getArtistById(bundle.getInt(ArtistDetailActivity.EXTRA_ARTIST_ID).toLong())
|
disposable.add(repository.getArtistByIdFlowable(bundle.getInt(ArtistDetailActivity.EXTRA_ARTIST_ID))
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showArtist(it) },
|
.subscribe({ this.showArtist(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -38,9 +38,7 @@ class ArtistPresenter(private val mView: ArtistContract.ArtistView) : Presenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadArtists() {
|
override fun loadArtists() {
|
||||||
disposable.add(repository.allArtists
|
disposable.add(repository.allArtistsFlowable
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { mView.loading() }
|
.doOnSubscribe { mView.loading() }
|
||||||
.subscribe({ this.showList(it) },
|
.subscribe({ this.showList(it) },
|
||||||
{ mView.showEmptyView() },
|
{ mView.showEmptyView() },
|
||||||
|
|
|
@ -36,9 +36,7 @@ class GenreDetailsPresenter(private val view: GenreDetailsContract.GenreDetailsV
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadGenre(genreId: Int) {
|
override fun loadGenre(genreId: Int) {
|
||||||
disposable.add(repository.getGenre(genreId)
|
disposable.add(repository.getGenreFlowable(genreId)
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showGenre(it) },
|
.subscribe({ this.showGenre(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -35,9 +35,7 @@ class GenrePresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadGenre() {
|
override fun loadGenre() {
|
||||||
disposable.add(repository.allGenres
|
disposable.add(repository.allGenresFlowable
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showList(it) },
|
.subscribe({ this.showList(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -53,7 +53,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRecentArtists() {
|
private fun loadRecentArtists() {
|
||||||
disposable += repository.recentArtists
|
disposable += repository.recentArtistsFlowable
|
||||||
.subscribe({
|
.subscribe({
|
||||||
if (it.isNotEmpty()) hashSet.add(Home(0, R.string.recent_artists, 0, it, RECENT_ARTISTS, R.drawable.ic_artist_white_24dp))
|
if (it.isNotEmpty()) hashSet.add(Home(0, R.string.recent_artists, 0, it, RECENT_ARTISTS, R.drawable.ic_artist_white_24dp))
|
||||||
view.showData(ArrayList(hashSet))
|
view.showData(ArrayList(hashSet))
|
||||||
|
@ -63,7 +63,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadRecentAlbums() {
|
private fun loadRecentAlbums() {
|
||||||
disposable += repository.recentAlbums
|
disposable += repository.recentAlbumsFlowable
|
||||||
.subscribe({
|
.subscribe({
|
||||||
if (it.isNotEmpty()) hashSet.add(Home(1, R.string.recent_albums, 0, it, RECENT_ALBUMS, R.drawable.ic_album_white_24dp))
|
if (it.isNotEmpty()) hashSet.add(Home(1, R.string.recent_albums, 0, it, RECENT_ALBUMS, R.drawable.ic_album_white_24dp))
|
||||||
view.showData(ArrayList(hashSet))
|
view.showData(ArrayList(hashSet))
|
||||||
|
@ -73,7 +73,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadATopAlbums() {
|
private fun loadATopAlbums() {
|
||||||
disposable += repository.topAlbums
|
disposable += repository.topAlbumsFlowable
|
||||||
.subscribe({
|
.subscribe({
|
||||||
if (it.isNotEmpty()) hashSet.add(Home(3, R.string.top_albums, 0, it, TOP_ALBUMS, R.drawable.ic_album_white_24dp))
|
if (it.isNotEmpty()) hashSet.add(Home(3, R.string.top_albums, 0, it, TOP_ALBUMS, R.drawable.ic_album_white_24dp))
|
||||||
view.showData(ArrayList(hashSet))
|
view.showData(ArrayList(hashSet))
|
||||||
|
@ -83,7 +83,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadTopArtists() {
|
private fun loadTopArtists() {
|
||||||
disposable += repository.topArtists
|
disposable += repository.topArtistsFlowable
|
||||||
.subscribe({
|
.subscribe({
|
||||||
if (it.isNotEmpty()) hashSet.add(Home(2, R.string.top_artists, 0, it, TOP_ARTISTS, R.drawable.ic_artist_white_24dp))
|
if (it.isNotEmpty()) hashSet.add(Home(2, R.string.top_artists, 0, it, TOP_ARTISTS, R.drawable.ic_artist_white_24dp))
|
||||||
view.showData(ArrayList(hashSet))
|
view.showData(ArrayList(hashSet))
|
||||||
|
@ -93,7 +93,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFavorite() {
|
private fun loadFavorite() {
|
||||||
disposable += repository.favoritePlaylist
|
disposable += repository.favoritePlaylistFlowable
|
||||||
.subscribe({
|
.subscribe({
|
||||||
if (it.isNotEmpty()) hashSet.add(Home(4, R.string.favorites, 0, it, PLAYLISTS, R.drawable.ic_favorite_white_24dp))
|
if (it.isNotEmpty()) hashSet.add(Home(4, R.string.favorites, 0, it, PLAYLISTS, R.drawable.ic_favorite_white_24dp))
|
||||||
view.showData(ArrayList(hashSet))
|
view.showData(ArrayList(hashSet))
|
||||||
|
|
|
@ -35,9 +35,7 @@ class PlaylistPresenter(private val view: PlaylistContract.PlaylistView) : Prese
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadPlaylists() {
|
override fun loadPlaylists() {
|
||||||
disposable.add(repository.allPlaylists
|
disposable.add(repository.allPlaylistsFlowable
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showList(it) },
|
.subscribe({ this.showList(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -35,9 +35,7 @@ class PlaylistSongsPresenter(private val view: PlaylistSongsContract.PlaylistSon
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadSongs(playlist: Playlist) {
|
override fun loadSongs(playlist: Playlist) {
|
||||||
disposable.add(repository.getPlaylistSongs(playlist)
|
disposable.add(repository.getPlaylistSongsFlowable(playlist)
|
||||||
.subscribeOn(schedulerProvider.io())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ songs -> view.showData(songs) },
|
.subscribe({ songs -> view.showData(songs) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -42,13 +42,6 @@ class SearchPresenter(private val view: SearchContract.SearchView) : Presenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun search(query: String?) {
|
override fun search(query: String?) {
|
||||||
disposable.add(repository.search(query)
|
view.showData(repository.search(query))
|
||||||
.debounce(500, TimeUnit.MILLISECONDS)
|
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
|
||||||
.subscribe({ this.showList(it) },
|
|
||||||
{ view.showEmptyView() },
|
|
||||||
{ view.completed() }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,7 @@ import java.util.*
|
||||||
class SongPresenter(private val view: SongContract.SongView) : Presenter(), SongContract.Presenter {
|
class SongPresenter(private val view: SongContract.SongView) : Presenter(), SongContract.Presenter {
|
||||||
|
|
||||||
override fun loadSongs() {
|
override fun loadSongs() {
|
||||||
disposable.add(repository.allSongs
|
disposable.add(repository.allSongsFlowable
|
||||||
.subscribeOn(schedulerProvider.computation())
|
|
||||||
.observeOn(schedulerProvider.ui())
|
|
||||||
.doOnSubscribe { view.loading() }
|
.doOnSubscribe { view.loading() }
|
||||||
.subscribe({ this.showList(it) },
|
.subscribe({ this.showList(it) },
|
||||||
{ view.showEmptyView() },
|
{ view.showEmptyView() },
|
||||||
|
|
|
@ -22,14 +22,15 @@ import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import code.name.monkey.retromusic.util.FileUtil;
|
import code.name.monkey.retromusic.util.FileUtil;
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||||
|
|
||||||
import static code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED;
|
import static code.name.monkey.retromusic.service.MusicService.MEDIA_STORE_CHANGED;
|
||||||
|
|
||||||
public class BlacklistStore extends SQLiteOpenHelper {
|
public class BlacklistStore extends SQLiteOpenHelper {
|
||||||
public static final String DATABASE_NAME = "blacklist.db";
|
public static final String DATABASE_NAME = "blacklist.db";
|
||||||
|
|
|
@ -20,20 +20,20 @@ import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.provider.MediaStore.Audio.AudioColumns;
|
import android.provider.MediaStore.Audio.AudioColumns;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
|
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
|
||||||
* <p/>
|
* <p/>
|
||||||
* This keeps track of the music playback and history state of the playback service
|
* This keeps track of the music playback and history state of the playback service
|
||||||
*/
|
*/
|
||||||
public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
|
public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
|
||||||
public static final String DATABASE_NAME = "music_playback_state.db";
|
public static final String DATABASE_NAME = "music_playback_state.db";
|
||||||
|
@ -187,17 +187,34 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Observable<ArrayList<Song>> getSavedPlayingQueue() {
|
public Observable<ArrayList<Song>> getSavedPlayingQueueFlowable() {
|
||||||
|
return getQueueFlowable(PLAYING_QUEUE_TABLE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Observable<ArrayList<Song>> getSavedOriginalPlayingQueueFlowable() {
|
||||||
|
return getQueueFlowable(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ArrayList<Song> getSavedPlayingQueue() {
|
||||||
return getQueue(PLAYING_QUEUE_TABLE_NAME);
|
return getQueue(PLAYING_QUEUE_TABLE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Observable<ArrayList<Song>> getSavedOriginalPlayingQueue() {
|
public ArrayList<Song> getSavedOriginalPlayingQueue() {
|
||||||
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private Observable<ArrayList<Song>> getQueue(@NonNull final String tableName) {
|
private Observable<ArrayList<Song>> getQueueFlowable(@NonNull final String tableName) {
|
||||||
|
Cursor cursor = getReadableDatabase().query(tableName, null,
|
||||||
|
null, null, null, null, null);
|
||||||
|
return SongLoader.INSTANCE.getSongsFlowable(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ArrayList<Song> getQueue(@NonNull final String tableName) {
|
||||||
Cursor cursor = getReadableDatabase().query(tableName, null,
|
Cursor cursor = getReadableDatabase().query(tableName, null,
|
||||||
null, null, null, null, null);
|
null, null, null, null, null);
|
||||||
return SongLoader.INSTANCE.getSongs(cursor);
|
return SongLoader.INSTANCE.getSongs(cursor);
|
||||||
|
|
|
@ -18,113 +18,163 @@ import android.content.Context
|
||||||
import code.name.monkey.retromusic.App
|
import code.name.monkey.retromusic.App
|
||||||
import code.name.monkey.retromusic.loaders.*
|
import code.name.monkey.retromusic.loaders.*
|
||||||
import code.name.monkey.retromusic.model.*
|
import code.name.monkey.retromusic.model.*
|
||||||
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
|
|
||||||
import code.name.monkey.retromusic.providers.interfaces.Repository
|
import code.name.monkey.retromusic.providers.interfaces.Repository
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
|
||||||
class RepositoryImpl(private val context: Context) : Repository {
|
class RepositoryImpl(private val context: Context) : Repository {
|
||||||
override val favoritePlaylist: Observable<ArrayList<Playlist>>
|
override fun search(query: String?): MutableList<Any> {
|
||||||
|
return SearchLoader.searchAll(context, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allAlbums(): ArrayList<Album> {
|
||||||
|
return AlbumLoader.getAllAlbums(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun recentAlbums(): ArrayList<Album> {
|
||||||
|
return LastAddedSongsLoader.getLastAddedAlbums(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun topAlbums(): ArrayList<Album> {
|
||||||
|
return TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allArtists(): ArrayList<Artist> {
|
||||||
|
return ArtistLoader.getAllArtists(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun recentArtists(): ArrayList<Artist> {
|
||||||
|
return LastAddedSongsLoader.getLastAddedArtists(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun topArtists(): ArrayList<Artist> {
|
||||||
|
return TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allPlaylists(): ArrayList<Playlist> {
|
||||||
|
return PlaylistLoader.getAllPlaylists(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun allGenres(): ArrayList<Genre> {
|
||||||
|
return GenreLoader.getAllGenres(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSongFlowable(id: Int): Observable<Song> {
|
||||||
|
return SongLoader.getSongFlowable(context, id)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAlbumFlowable(albumId: Int): Observable<Album> {
|
||||||
|
return AlbumLoader.getAlbumFlowable(context, albumId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getArtistByIdFlowable(artistId: Int): Observable<Artist> {
|
||||||
|
return ArtistLoader.getArtistFlowable(context, artistId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getPlaylistSongsFlowable(playlist: Playlist): Observable<ArrayList<Song>> {
|
||||||
|
return PlaylistSongsLoader.getPlaylistSongListFlowable(context, playlist)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getGenreFlowable(genreId: Int): Observable<ArrayList<Song>> {
|
||||||
|
return GenreLoader.getSongsFlowable(context, genreId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
override val favoritePlaylist: ArrayList<Playlist>
|
||||||
get() = PlaylistLoader.getFavoritePlaylist(context)
|
get() = PlaylistLoader.getFavoritePlaylist(context)
|
||||||
|
|
||||||
|
override fun allSongs(): ArrayList<Song> {
|
||||||
|
return SongLoader.getAllSongs(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val favoritePlaylistFlowable: Observable<ArrayList<Playlist>>
|
||||||
|
get() = PlaylistLoader.getFavoritePlaylistFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
|
|
||||||
override val allSongs: Observable<ArrayList<Song>>
|
override val allSongsFlowable: Observable<ArrayList<Song>>
|
||||||
get() = SongLoader.getAllSongs(context)
|
get() = SongLoader.getAllSongsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val suggestionSongs: Observable<ArrayList<Song>>
|
override val suggestionSongsFlowable: Observable<ArrayList<Song>>
|
||||||
get() = SongLoader.suggestSongs(context)
|
get() = SongLoader.suggestSongs(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val allAlbums: Observable<ArrayList<Album>>
|
override val allAlbumsFlowable: Observable<ArrayList<Album>>
|
||||||
get() = AlbumLoader.getAllAlbums(context)
|
get() = AlbumLoader.getAllAlbumsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val recentAlbums: Observable<ArrayList<Album>>
|
override val recentAlbumsFlowable: Observable<ArrayList<Album>>
|
||||||
get() = LastAddedSongsLoader.getLastAddedAlbums(context)
|
get() = LastAddedSongsLoader.getLastAddedAlbumsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val topAlbums: Observable<ArrayList<Album>>
|
override val topAlbumsFlowable: Observable<ArrayList<Album>>
|
||||||
get() = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
|
get() = TopAndRecentlyPlayedTracksLoader.getTopAlbumsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val allArtists: Observable<ArrayList<Artist>>
|
override val allArtistsFlowable: Observable<ArrayList<Artist>>
|
||||||
get() = ArtistLoader.getAllArtists(context)
|
get() = ArtistLoader.getAllArtistsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val recentArtists: Observable<ArrayList<Artist>>
|
override val recentArtistsFlowable: Observable<ArrayList<Artist>>
|
||||||
get() = LastAddedSongsLoader.getLastAddedArtists(context)
|
get() = LastAddedSongsLoader.getLastAddedArtistsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val topArtists: Observable<ArrayList<Artist>>
|
override val topArtistsFlowable: Observable<ArrayList<Artist>>
|
||||||
get() = TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
|
get() = TopAndRecentlyPlayedTracksLoader.getTopArtistsFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val allPlaylists: Observable<ArrayList<Playlist>>
|
override val allPlaylistsFlowable: Observable<ArrayList<Playlist>>
|
||||||
get() = PlaylistLoader.getAllPlaylists(context)
|
get() = PlaylistLoader.getAllPlaylistsFlowoable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val homeList: Observable<ArrayList<Playlist>>
|
|
||||||
get() = HomeLoader.getHomeLoader(context)
|
override val allGenresFlowable: Observable<ArrayList<Genre>>
|
||||||
|
get() = GenreLoader.getAllGenresFlowable(context)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
override val allThings: Observable<ArrayList<AbsSmartPlaylist>>
|
override fun getSong(id: Int): Song {
|
||||||
get() = HomeLoader.getRecentAndTopThings(context)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
|
|
||||||
override val allGenres: Observable<ArrayList<Genre>>
|
|
||||||
get() = GenreLoader.getAllGenres(context)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
|
|
||||||
override fun getSong(id: Int): Observable<Song> {
|
|
||||||
return SongLoader.getSong(context, id)
|
return SongLoader.getSong(context, id)
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAlbum(albumId: Int): Observable<Album> {
|
override fun getAlbum(albumId: Int): Album {
|
||||||
return AlbumLoader.getAlbum(context, albumId)
|
return AlbumLoader.getAlbum(context, albumId)
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getArtistById(artistId: Long): Observable<Artist> {
|
override fun getArtistById(artistId: Long): Artist {
|
||||||
return ArtistLoader.getArtist(context, artistId.toInt())
|
return ArtistLoader.getArtist(context, artistId.toInt())
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun search(query: String?): Observable<ArrayList<Any>> {
|
override fun getPlaylistSongs(playlist: Playlist): ArrayList<Song> {
|
||||||
return SearchLoader.searchAll(context, query)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPlaylistSongs(playlist: Playlist): Observable<ArrayList<Song>> {
|
|
||||||
return PlaylistSongsLoader.getPlaylistSongList(context, playlist)
|
return PlaylistSongsLoader.getPlaylistSongList(context, playlist)
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGenre(genreId: Int): Observable<ArrayList<Song>> {
|
override fun getGenre(genreId: Int): ArrayList<Song> {
|
||||||
return GenreLoader.getSongs(context, genreId)
|
return GenreLoader.getSongs(context, genreId)
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,7 @@
|
||||||
package code.name.monkey.retromusic.providers.interfaces
|
package code.name.monkey.retromusic.providers.interfaces
|
||||||
|
|
||||||
import code.name.monkey.retromusic.model.*
|
import code.name.monkey.retromusic.model.*
|
||||||
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 11/08/17.
|
* Created by hemanths on 11/08/17.
|
||||||
|
@ -25,42 +23,68 @@ import kotlin.collections.ArrayList
|
||||||
|
|
||||||
interface Repository {
|
interface Repository {
|
||||||
|
|
||||||
val allSongs: Observable<ArrayList<Song>>
|
val allSongsFlowable: Observable<ArrayList<Song>>
|
||||||
|
|
||||||
val suggestionSongs: Observable<ArrayList<Song>>
|
fun allSongs(): ArrayList<Song>
|
||||||
|
|
||||||
val allAlbums: Observable<ArrayList<Album>>
|
val suggestionSongsFlowable: Observable<ArrayList<Song>>
|
||||||
|
|
||||||
val recentAlbums: Observable<ArrayList<Album>>
|
val allAlbumsFlowable: Observable<ArrayList<Album>>
|
||||||
|
|
||||||
val topAlbums: Observable<ArrayList<Album>>
|
fun allAlbums(): ArrayList<Album>
|
||||||
|
|
||||||
val allArtists: Observable<ArrayList<Artist>>
|
val recentAlbumsFlowable: Observable<ArrayList<Album>>
|
||||||
|
|
||||||
val recentArtists: Observable<ArrayList<Artist>>
|
fun recentAlbums(): ArrayList<Album>
|
||||||
|
|
||||||
val topArtists: Observable<ArrayList<Artist>>
|
val topAlbumsFlowable: Observable<ArrayList<Album>>
|
||||||
|
|
||||||
val allPlaylists: Observable<ArrayList<Playlist>>
|
fun topAlbums(): ArrayList<Album>
|
||||||
|
|
||||||
val homeList: Observable<ArrayList<Playlist>>
|
val allArtistsFlowable: Observable<ArrayList<Artist>>
|
||||||
|
|
||||||
val allThings: Observable<ArrayList<AbsSmartPlaylist>>
|
fun allArtists(): ArrayList<Artist>
|
||||||
|
|
||||||
val allGenres: Observable<ArrayList<Genre>>
|
val recentArtistsFlowable: Observable<ArrayList<Artist>>
|
||||||
|
|
||||||
fun getSong(id: Int): Observable<Song>
|
fun recentArtists(): ArrayList<Artist>
|
||||||
|
|
||||||
fun getAlbum(albumId: Int): Observable<Album>
|
val topArtistsFlowable: Observable<ArrayList<Artist>>
|
||||||
|
|
||||||
fun getArtistById(artistId: Long): Observable<Artist>
|
fun topArtists(): ArrayList<Artist>
|
||||||
|
|
||||||
fun search(query: String?): Observable<ArrayList<Any>>
|
val allPlaylistsFlowable: Observable<ArrayList<Playlist>>
|
||||||
|
|
||||||
fun getPlaylistSongs(playlist: Playlist): Observable<ArrayList<Song>>
|
fun allPlaylists(): ArrayList<Playlist>
|
||||||
|
|
||||||
fun getGenre(genreId: Int): Observable<ArrayList<Song>>
|
val allGenresFlowable: Observable<ArrayList<Genre>>
|
||||||
|
|
||||||
val favoritePlaylist: Observable<ArrayList<Playlist>>
|
fun allGenres(): ArrayList<Genre>
|
||||||
|
|
||||||
|
fun getSongFlowable(id: Int): Observable<Song>
|
||||||
|
|
||||||
|
fun getSong(id: Int): Song
|
||||||
|
|
||||||
|
fun getAlbumFlowable(albumId: Int): Observable<Album>
|
||||||
|
|
||||||
|
fun getAlbum(albumId: Int): Album
|
||||||
|
|
||||||
|
fun getArtistByIdFlowable(artistId: Int): Observable<Artist>
|
||||||
|
|
||||||
|
fun getArtistById(artistId: Long): Artist
|
||||||
|
|
||||||
|
fun search(query: String?): MutableList<Any>
|
||||||
|
|
||||||
|
fun getPlaylistSongsFlowable(playlist: Playlist): Observable<ArrayList<Song>>
|
||||||
|
|
||||||
|
fun getPlaylistSongs(playlist: Playlist): ArrayList<Song>
|
||||||
|
|
||||||
|
fun getGenreFlowable(genreId: Int): Observable<ArrayList<Song>>
|
||||||
|
|
||||||
|
fun getGenre(genreId: Int): ArrayList<Song>
|
||||||
|
|
||||||
|
val favoritePlaylistFlowable: Observable<ArrayList<Playlist>>
|
||||||
|
|
||||||
|
val favoritePlaylist: ArrayList<Playlist>
|
||||||
|
|
||||||
}
|
}
|
|
@ -27,12 +27,7 @@ import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import code.name.monkey.retromusic.BuildConfig
|
import code.name.monkey.retromusic.BuildConfig
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_PAUSE
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_PLAY
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_REWIND
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_SKIP
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_STOP
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
import code.name.monkey.retromusic.auto.AutoMediaIDHelper
|
||||||
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||||
|
import code.name.monkey.retromusic.helper.ShuffleHelper
|
||||||
|
import code.name.monkey.retromusic.loaders.*
|
||||||
|
import code.name.monkey.retromusic.model.Song
|
||||||
|
import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.TOGGLE_FAVORITE
|
||||||
|
import code.name.monkey.retromusic.util.MusicUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by hemanths on 2019-08-01.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MediaSessionCallback(private val context: Context,
|
||||||
|
private val musicService: MusicService) : MediaSessionCompat.Callback() {
|
||||||
|
|
||||||
|
override fun onPlay() {
|
||||||
|
super.onPlay()
|
||||||
|
musicService.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
musicService.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSkipToNext() {
|
||||||
|
super.onSkipToNext()
|
||||||
|
musicService.playNextSong(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSkipToPrevious() {
|
||||||
|
super.onSkipToPrevious()
|
||||||
|
musicService.back(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
musicService.quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSeekTo(pos: Long) {
|
||||||
|
super.onSeekTo(pos)
|
||||||
|
musicService.seek(pos.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
|
||||||
|
return MediaButtonIntentReceiver.handleIntent(context, mediaButtonIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
|
||||||
|
super.onPlayFromMediaId(mediaId, extras)
|
||||||
|
|
||||||
|
val musicId = mediaId?.let { AutoMediaIDHelper.extractMusicID(it) }
|
||||||
|
val itemId = musicId?.toInt() ?: -1
|
||||||
|
val songs = arrayListOf<Song>()
|
||||||
|
when (val category = mediaId?.let { AutoMediaIDHelper.extractCategory(it) }) {
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ALBUM -> {
|
||||||
|
val album = AlbumLoader.getAlbum(context, itemId)
|
||||||
|
album.songs?.let { songs.addAll(it) }
|
||||||
|
openQueue(songs, 0, true)
|
||||||
|
}
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_ARTIST -> {
|
||||||
|
val artist = ArtistLoader.getArtist(context, itemId)
|
||||||
|
songs.addAll(artist.songs)
|
||||||
|
openQueue(songs, 0, true)
|
||||||
|
}
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_PLAYLIST -> {
|
||||||
|
val playlist = PlaylistLoader.getPlaylist(context, itemId)
|
||||||
|
songs.addAll(playlist.getSongs(context))
|
||||||
|
openQueue(songs, 0, true)
|
||||||
|
}
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY,
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS,
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_QUEUE -> {
|
||||||
|
val tracks: List<Song>
|
||||||
|
if (category.equals(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY)) {
|
||||||
|
tracks = TopAndRecentlyPlayedTracksLoader.getRecentlyPlayedTracks(context)
|
||||||
|
} else if (category.equals(AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS)) {
|
||||||
|
tracks = TopAndRecentlyPlayedTracksLoader.getTopTracks(context)
|
||||||
|
} else {
|
||||||
|
tracks = MusicPlaybackQueueStore.getInstance(context).savedOriginalPlayingQueue
|
||||||
|
}
|
||||||
|
songs.addAll(tracks)
|
||||||
|
var songIndex = MusicUtil.indexOfSongInList(tracks, itemId)
|
||||||
|
if (songIndex == -1) {
|
||||||
|
songIndex = 0
|
||||||
|
}
|
||||||
|
openQueue(songs, songIndex, true)
|
||||||
|
}
|
||||||
|
AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SHUFFLE -> {
|
||||||
|
val allSongs = SongLoader.getAllSongs(context)
|
||||||
|
ShuffleHelper.makeShuffleList(allSongs, -1)
|
||||||
|
openQueue(allSongs, 0, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCustomAction(action: String, extras: Bundle?) {
|
||||||
|
when (action) {
|
||||||
|
/* CYCLE_REPEAT -> {
|
||||||
|
cycleRepeatMode()
|
||||||
|
musicService.updateMediaSessionPlaybackState()
|
||||||
|
}
|
||||||
|
|
||||||
|
TOGGLE_SHUFFLE -> {
|
||||||
|
musicService.toggleShuffle()
|
||||||
|
musicService.updateMediaSessionPlaybackState()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
TOGGLE_FAVORITE -> {
|
||||||
|
MusicUtil.toggleFavorite(context, MusicPlayerRemote.currentSong)
|
||||||
|
musicService.updateMediaSessionPlaybackState()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
println("Unsupported action: $action")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkAndStartPlaying(songs: ArrayList<Song>, itemId: Int) {
|
||||||
|
var songIndex = MusicUtil.indexOfSongInList(songs, itemId)
|
||||||
|
if (songIndex == -1) {
|
||||||
|
songIndex = 0
|
||||||
|
}
|
||||||
|
openQueue(songs, songIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openQueue(songs: ArrayList<Song>, index: Int, startPlaying: Boolean = true) {
|
||||||
|
MusicPlayerRemote.openQueue(songs, index, startPlaying)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
public class MediaStoreObserver extends ContentObserver implements Runnable {
|
||||||
|
// milliseconds to delay before calling refresh to aggregate events
|
||||||
|
private static final long REFRESH_DELAY = 500;
|
||||||
|
private final MusicService musicService;
|
||||||
|
private Handler mHandler;
|
||||||
|
|
||||||
|
MediaStoreObserver(MusicService musicService, Handler handler) {
|
||||||
|
super(handler);
|
||||||
|
this.musicService = musicService;
|
||||||
|
mHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
// if a change is detected, remove any scheduled callback
|
||||||
|
// then post a new one. This is intended to prevent closely
|
||||||
|
// spaced events from generating multiple refresh calls
|
||||||
|
mHandler.removeCallbacks(this);
|
||||||
|
mHandler.postDelayed(this, REFRESH_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// actually call refresh when the delayed callback fires
|
||||||
|
// do not send a sticky broadcast here
|
||||||
|
musicService.handleAndSendChangeInternal(MusicService.MEDIA_STORE_CHANGED);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,6 @@
|
||||||
package code.name.monkey.retromusic.service;
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
|
||||||
import android.appwidget.AppWidgetManager;
|
import android.appwidget.AppWidgetManager;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
@ -31,15 +30,15 @@ import android.media.AudioManager;
|
||||||
import android.media.audiofx.AudioEffect;
|
import android.media.audiofx.AudioEffect;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
import android.support.v4.media.MediaBrowserCompat;
|
||||||
import android.support.v4.media.MediaMetadataCompat;
|
import android.support.v4.media.MediaMetadataCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
|
@ -50,10 +49,10 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media.MediaBrowserServiceCompat;
|
||||||
|
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -64,15 +63,14 @@ import code.name.monkey.retromusic.appwidgets.AppWidgetCard;
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetClassic;
|
import code.name.monkey.retromusic.appwidgets.AppWidgetClassic;
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetSmall;
|
import code.name.monkey.retromusic.appwidgets.AppWidgetSmall;
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetText;
|
import code.name.monkey.retromusic.appwidgets.AppWidgetText;
|
||||||
|
import code.name.monkey.retromusic.auto.AutoMediaIDHelper;
|
||||||
|
import code.name.monkey.retromusic.auto.AutoMusicProvider;
|
||||||
import code.name.monkey.retromusic.glide.BlurTransformation;
|
import code.name.monkey.retromusic.glide.BlurTransformation;
|
||||||
import code.name.monkey.retromusic.glide.GlideApp;
|
import code.name.monkey.retromusic.glide.GlideApp;
|
||||||
import code.name.monkey.retromusic.glide.GlideRequest;
|
import code.name.monkey.retromusic.glide.GlideRequest;
|
||||||
import code.name.monkey.retromusic.glide.RetroGlideExtension;
|
import code.name.monkey.retromusic.glide.RetroGlideExtension;
|
||||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget;
|
import code.name.monkey.retromusic.glide.RetroSimpleTarget;
|
||||||
import code.name.monkey.retromusic.helper.ShuffleHelper;
|
import code.name.monkey.retromusic.helper.ShuffleHelper;
|
||||||
import code.name.monkey.retromusic.helper.StopWatch;
|
|
||||||
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
|
|
||||||
import code.name.monkey.retromusic.model.AbsCustomPlaylist;
|
|
||||||
import code.name.monkey.retromusic.model.Playlist;
|
import code.name.monkey.retromusic.model.Playlist;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import code.name.monkey.retromusic.providers.HistoryStore;
|
import code.name.monkey.retromusic.providers.HistoryStore;
|
||||||
|
@ -83,57 +81,73 @@ import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl2
|
||||||
import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo;
|
import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo;
|
||||||
import code.name.monkey.retromusic.service.playback.Playback;
|
import code.name.monkey.retromusic.service.playback.Playback;
|
||||||
import code.name.monkey.retromusic.util.MusicUtil;
|
import code.name.monkey.retromusic.util.MusicUtil;
|
||||||
|
import code.name.monkey.retromusic.util.PackageValidator;
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||||
import code.name.monkey.retromusic.util.RetroUtil;
|
import code.name.monkey.retromusic.util.RetroUtil;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_PAUSE;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_PENDING_QUIT;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_PLAY;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_PLAY_PLAYLIST;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_QUIT;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_REWIND;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_SKIP;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_STOP;
|
|
||||||
import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE;
|
|
||||||
import static code.name.monkey.retromusic.Constants.APP_WIDGET_UPDATE;
|
|
||||||
import static code.name.monkey.retromusic.Constants.EXTRA_APP_WIDGET_NAME;
|
|
||||||
import static code.name.monkey.retromusic.Constants.INTENT_EXTRA_PLAYLIST;
|
|
||||||
import static code.name.monkey.retromusic.Constants.INTENT_EXTRA_SHUFFLE_MODE;
|
|
||||||
import static code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED;
|
|
||||||
import static code.name.monkey.retromusic.Constants.META_CHANGED;
|
|
||||||
import static code.name.monkey.retromusic.Constants.MUSIC_PACKAGE_NAME;
|
|
||||||
import static code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED;
|
|
||||||
import static code.name.monkey.retromusic.Constants.QUEUE_CHANGED;
|
|
||||||
import static code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED;
|
|
||||||
import static code.name.monkey.retromusic.Constants.RETRO_MUSIC_PACKAGE_NAME;
|
|
||||||
import static code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
|
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
|
||||||
*/
|
*/
|
||||||
public class MusicService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks {
|
public class MusicService extends MediaBrowserServiceCompat implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks {
|
||||||
public static final String TAG = MusicService.class.getSimpleName();
|
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 = "POSITION";
|
||||||
public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK";
|
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_SHUFFLE_MODE = "SHUFFLE_MODE";
|
||||||
public static final String SAVED_REPEAT_MODE = "REPEAT_MODE";
|
public static final String SAVED_REPEAT_MODE = "REPEAT_MODE";
|
||||||
|
|
||||||
public static final int RELEASE_WAKELOCK = 0;
|
public static final int RELEASE_WAKELOCK = 0;
|
||||||
public static final int TRACK_ENDED = 1;
|
public static final int TRACK_ENDED = 1;
|
||||||
public static final int TRACK_WENT_TO_NEXT = 2;
|
public static final int TRACK_WENT_TO_NEXT = 2;
|
||||||
public static final int PLAY_SONG = 3;
|
public static final int PLAY_SONG = 3;
|
||||||
public static final int PREPARE_NEXT = 4;
|
public static final int PREPARE_NEXT = 4;
|
||||||
public static final int SET_POSITION = 5;
|
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 RESTORE_QUEUES = 9;
|
||||||
|
|
||||||
public static final int SHUFFLE_MODE_NONE = 0;
|
public static final int SHUFFLE_MODE_NONE = 0;
|
||||||
public static final int SHUFFLE_MODE_SHUFFLE = 1;
|
public static final int SHUFFLE_MODE_SHUFFLE = 1;
|
||||||
|
|
||||||
public static final int REPEAT_MODE_NONE = 0;
|
public static final int REPEAT_MODE_NONE = 0;
|
||||||
public static final int REPEAT_MODE_ALL = 1;
|
public static final int REPEAT_MODE_ALL = 1;
|
||||||
public static final int REPEAT_MODE_THIS = 2;
|
public static final int REPEAT_MODE_THIS = 2;
|
||||||
|
|
||||||
public static final int SAVE_QUEUES = 0;
|
public static final int SAVE_QUEUES = 0;
|
||||||
private static final int FOCUS_CHANGE = 6;
|
|
||||||
private static final int DUCK = 7;
|
|
||||||
private static final int UNDUCK = 8;
|
|
||||||
private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY
|
private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY
|
||||||
| PlaybackStateCompat.ACTION_PAUSE
|
| PlaybackStateCompat.ACTION_PAUSE
|
||||||
| PlaybackStateCompat.ACTION_PLAY_PAUSE
|
| PlaybackStateCompat.ACTION_PLAY_PAUSE
|
||||||
|
@ -143,6 +157,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
| PlaybackStateCompat.ACTION_SEEK_TO;
|
| PlaybackStateCompat.ACTION_SEEK_TO;
|
||||||
private final IBinder musicBind = new MusicBinder();
|
private final IBinder musicBind = new MusicBinder();
|
||||||
public boolean pendingQuit = false;
|
public boolean pendingQuit = false;
|
||||||
|
public Playback playback;
|
||||||
|
public int position = -1;
|
||||||
|
public int nextPosition = -1;
|
||||||
private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance();
|
private AppWidgetBig appWidgetBig = AppWidgetBig.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();
|
||||||
|
@ -179,11 +196,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private Playback playback;
|
|
||||||
private ArrayList<Song> playingQueue = new ArrayList<>();
|
private ArrayList<Song> playingQueue = new ArrayList<>();
|
||||||
private ArrayList<Song> originalPlayingQueue = new ArrayList<>();
|
private ArrayList<Song> originalPlayingQueue = new ArrayList<>();
|
||||||
private int position = -1;
|
|
||||||
private int nextPosition = -1;
|
|
||||||
private int shuffleMode;
|
private int shuffleMode;
|
||||||
private int repeatMode;
|
private int repeatMode;
|
||||||
private boolean queuesRestored;
|
private boolean queuesRestored;
|
||||||
|
@ -198,7 +212,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
};
|
};
|
||||||
private PlayingNotification playingNotification;
|
private PlayingNotification playingNotification;
|
||||||
private AudioManager audioManager;
|
private AudioManager audioManager;
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private MediaSessionCompat mediaSession;
|
private MediaSessionCompat mediaSession;
|
||||||
private PowerManager.WakeLock wakeLock;
|
private PowerManager.WakeLock wakeLock;
|
||||||
private PlaybackHandler playerHandler;
|
private PlaybackHandler playerHandler;
|
||||||
|
@ -235,7 +248,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
super.onCallStateChanged(state, incomingNumber);
|
super.onCallStateChanged(state, incomingNumber);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private boolean isServiceBound;
|
|
||||||
private Handler uiThreadHandler;
|
private Handler uiThreadHandler;
|
||||||
private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
||||||
private boolean headsetReceiverRegistered = false;
|
private boolean headsetReceiverRegistered = false;
|
||||||
|
@ -260,6 +272,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
private PackageValidator mPackageValidator;
|
||||||
|
private AutoMusicProvider mMusicProvider;
|
||||||
|
|
||||||
private static String getTrackUri(@NonNull Song song) {
|
private static String getTrackUri(@NonNull Song song) {
|
||||||
return MusicUtil.getSongFileUri(song.getId()).toString();
|
return MusicUtil.getSongFileUri(song.getId()).toString();
|
||||||
|
@ -278,7 +292,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
@ -314,8 +327,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
|
|
||||||
initNotification();
|
initNotification();
|
||||||
|
|
||||||
mediaStoreObserver = new MediaStoreObserver(playerHandler);
|
mediaStoreObserver = new MediaStoreObserver(this, playerHandler);
|
||||||
throttledSeekHandler = new ThrottledSeekHandler(playerHandler);
|
throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler);
|
||||||
getContentResolver().registerContentObserver(
|
getContentResolver().registerContentObserver(
|
||||||
MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
|
MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
|
||||||
getContentResolver().registerContentObserver(
|
getContentResolver().registerContentObserver(
|
||||||
|
@ -324,14 +337,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this);
|
PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this);
|
||||||
|
|
||||||
restoreState();
|
restoreState();
|
||||||
|
mPackageValidator = new PackageValidator(this, R.xml.allowed_media_browser_callers);
|
||||||
mediaSession.setActive(true);
|
mMusicProvider = new AutoMusicProvider(this);
|
||||||
|
|
||||||
sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED"));
|
sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED"));
|
||||||
|
|
||||||
registerHeadsetEvents();
|
registerHeadsetEvents();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AudioManager getAudioManager() {
|
private AudioManager getAudioManager() {
|
||||||
|
@ -354,50 +364,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
"RetroMusicPlayer",
|
"RetroMusicPlayer",
|
||||||
mediaButtonReceiverComponentName,
|
mediaButtonReceiverComponentName,
|
||||||
mediaButtonReceiverPendingIntent);
|
mediaButtonReceiverPendingIntent);
|
||||||
mediaSession.setCallback(new MediaSessionCompat.Callback() {
|
MediaSessionCallback mediasessionCallback = new MediaSessionCallback(getApplicationContext(), this);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlay() {
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSkipToNext() {
|
|
||||||
playNextSong(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSkipToPrevious() {
|
|
||||||
back(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSeekTo(long pos) {
|
|
||||||
seek((int) pos);
|
|
||||||
updateMediaSessionPlaybackState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
|
|
||||||
return MediaButtonIntentReceiver.Companion.handleIntent(MusicService.this, mediaButtonEvent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
|
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
|
||||||
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
|
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
|
||||||
);
|
);
|
||||||
|
mediaSession.setCallback(mediasessionCallback);
|
||||||
|
mediaSession.setActive(true);
|
||||||
mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent);
|
mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent);
|
||||||
|
setSessionToken(mediaSession.getSessionToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -437,6 +411,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
case ACTION_PENDING_QUIT:
|
case ACTION_PENDING_QUIT:
|
||||||
pendingQuit = true;
|
pendingQuit = true;
|
||||||
break;
|
break;
|
||||||
|
case TOGGLE_FAVORITE:
|
||||||
|
MusicUtil.toggleFavorite(getApplicationContext(), getCurrentSong());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,22 +424,21 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
private void playFromPlaylist(Intent intent) {
|
private void playFromPlaylist(Intent intent) {
|
||||||
Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST);
|
Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST);
|
||||||
int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode());
|
int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode());
|
||||||
|
|
||||||
if (playlist != null) {
|
if (playlist != null) {
|
||||||
if (playlist instanceof AbsCustomPlaylist) {
|
ArrayList<Song> playlistSongs = playlist.getSongs(getApplicationContext());
|
||||||
((AbsCustomPlaylist) playlist).getSongs(getApplicationContext())
|
if (!playlistSongs.isEmpty()) {
|
||||||
.subscribeOn(Schedulers.io())
|
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
|
||||||
.subscribe(songs -> {
|
int startPosition = 0;
|
||||||
playSongs(shuffleMode, songs);
|
if (!playlistSongs.isEmpty()) {
|
||||||
}, throwable -> {
|
startPosition = new Random().nextInt(playlistSongs.size());
|
||||||
});
|
}
|
||||||
|
openQueue(playlistSongs, startPosition, true);
|
||||||
|
setShuffleMode(shuffleMode);
|
||||||
|
} else {
|
||||||
|
openQueue(playlistSongs, 0, true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
PlaylistSongsLoader.INSTANCE.getPlaylistSongList(getApplicationContext(), playlist.id)
|
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.subscribe(songs -> {
|
|
||||||
playSongs(shuffleMode, songs);
|
|
||||||
}, throwable -> {
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
|
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
|
||||||
|
@ -507,25 +483,43 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
isServiceBound = true;
|
|
||||||
return musicBind;
|
return musicBind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) {
|
||||||
|
if (!mPackageValidator.isKnownCaller(clientPackageName, clientUid)) {
|
||||||
|
return new BrowserRoot(AutoMediaIDHelper.MEDIA_ID_EMPTY_ROOT, null);
|
||||||
|
}
|
||||||
|
return new BrowserRoot(AutoMediaIDHelper.MEDIA_ID_ROOT, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
|
||||||
|
if (AutoMediaIDHelper.MEDIA_ID_EMPTY_ROOT.equals(parentId)) {
|
||||||
|
result.sendResult(new ArrayList<>());
|
||||||
|
} else if (mMusicProvider.isInitialized()) {
|
||||||
|
result.sendResult(mMusicProvider.getChildren(parentId, getResources()));
|
||||||
|
} else {
|
||||||
|
result.detach();
|
||||||
|
mMusicProvider.retrieveMediaAsync(success -> result.sendResult(mMusicProvider.getChildren(parentId, getResources())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRebind(Intent intent) {
|
public void onRebind(Intent intent) {
|
||||||
isServiceBound = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onUnbind(Intent intent) {
|
public boolean onUnbind(Intent intent) {
|
||||||
isServiceBound = false;
|
|
||||||
if (!isPlaying()) {
|
if (!isPlaying()) {
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveQueuesImpl() {
|
public void saveQueuesImpl() {
|
||||||
MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue);
|
MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,7 +527,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, getPosition()).apply();
|
PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, getPosition()).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void savePositionInTrack() {
|
public void savePositionInTrack() {
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()).apply();
|
PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,14 +552,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playerHandler.sendEmptyMessage(RESTORE_QUEUES);
|
playerHandler.sendEmptyMessage(RESTORE_QUEUES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void restoreQueuesAndPositionIfNecessary() {
|
public synchronized void restoreQueuesAndPositionIfNecessary() {
|
||||||
if (!queuesRestored && playingQueue.isEmpty()) {
|
if (!queuesRestored && playingQueue.isEmpty()) {
|
||||||
ArrayList<Song> restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue()
|
ArrayList<Song> restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue();
|
||||||
.blockingFirst();
|
ArrayList<Song> restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue();
|
||||||
|
|
||||||
ArrayList<Song> restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue()
|
|
||||||
.blockingFirst();
|
|
||||||
|
|
||||||
int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1);
|
int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1);
|
||||||
int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1);
|
int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1);
|
||||||
|
|
||||||
|
@ -587,20 +577,16 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
queuesRestored = true;
|
queuesRestored = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int quit() {
|
public void quit() {
|
||||||
pause();
|
pause();
|
||||||
playingNotification.stop();
|
playingNotification.stop();
|
||||||
|
|
||||||
if (isServiceBound) {
|
closeAudioEffectSession();
|
||||||
return START_STICKY;
|
getAudioManager().abandonAudioFocus(audioFocusListener);
|
||||||
} else {
|
stopSelf();
|
||||||
closeAudioEffectSession();
|
|
||||||
getAudioManager().abandonAudioFocus(audioFocusListener);
|
|
||||||
stopSelf();
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void releaseResources() {
|
private void releaseResources() {
|
||||||
playerHandler.removeCallbacksAndMessages(null);
|
playerHandler.removeCallbacksAndMessages(null);
|
||||||
musicPlayerHandlerThread.quitSafely();
|
musicPlayerHandlerThread.quitSafely();
|
||||||
|
@ -629,7 +615,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playSongAt(getNextPosition(force));
|
playSongAt(getNextPosition(force));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openTrackAndPrepareNextAt(int position) {
|
public boolean openTrackAndPrepareNextAt(int position) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
boolean prepared = openCurrent();
|
boolean prepared = openCurrent();
|
||||||
|
@ -655,7 +641,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
|
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean prepareNextImpl() {
|
public boolean prepareNextImpl() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
try {
|
try {
|
||||||
int nextPosition = getNextPosition(false);
|
int nextPosition = getNextPosition(false);
|
||||||
|
@ -694,13 +680,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMediaSessionPlaybackState() {
|
public void updateMediaSessionPlaybackState() {
|
||||||
mediaSession.setPlaybackState(
|
PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder()
|
||||||
new PlaybackStateCompat.Builder()
|
.setActions(MEDIA_SESSION_ACTIONS)
|
||||||
.setActions(MEDIA_SESSION_ACTIONS)
|
.setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
|
||||||
.setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
|
getSongProgressMillis(), 1);
|
||||||
getSongProgressMillis(), 1)
|
|
||||||
.build());
|
|
||||||
|
mediaSession.setPlaybackState(stateBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMediaSessionMetaData() {
|
private void updateMediaSessionMetaData() {
|
||||||
|
@ -801,7 +788,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLastTrack() {
|
public boolean isLastTrack() {
|
||||||
return getPosition() == getPlayingQueue().size() - 1;
|
return getPosition() == getPlayingQueue().size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,7 +933,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget();
|
playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSongAtImpl(int position) {
|
public void playSongAtImpl(int position) {
|
||||||
if (openTrackAndPrepareNextAt(position)) {
|
if (openTrackAndPrepareNextAt(position)) {
|
||||||
play();
|
play();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1128,18 +1115,18 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
notifyChange(QUEUE_CHANGED);
|
notifyChange(QUEUE_CHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyChange(@NonNull final String what) {
|
public void notifyChange(@NonNull final String what) {
|
||||||
handleAndSendChangeInternal(what);
|
handleAndSendChangeInternal(what);
|
||||||
sendPublicIntent(what);
|
sendPublicIntent(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAndSendChangeInternal(@NonNull final String what) {
|
public void handleAndSendChangeInternal(@NonNull final String what) {
|
||||||
handleChangeInternal(what);
|
handleChangeInternal(what);
|
||||||
sendChangeInternal(what);
|
sendChangeInternal(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
// to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch
|
// to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch
|
||||||
private void sendPublicIntent(@NonNull final String what) {
|
public void sendPublicIntent(@NonNull final String what) {
|
||||||
final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
|
final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME));
|
||||||
|
|
||||||
final Song song = getCurrentSong();
|
final Song song = getCurrentSong();
|
||||||
|
@ -1266,187 +1253,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
playerHandler.sendEmptyMessage(TRACK_ENDED);
|
playerHandler.sendEmptyMessage(TRACK_ENDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPausedByTransientLossOfFocus() {
|
||||||
private static final class QueueSaveHandler extends Handler {
|
return pausedByTransientLossOfFocus;
|
||||||
@NonNull
|
|
||||||
private final WeakReference<MusicService> mService;
|
|
||||||
|
|
||||||
QueueSaveHandler(final MusicService service, @NonNull final Looper looper) {
|
|
||||||
super(looper);
|
|
||||||
mService = new WeakReference<>(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(@NonNull Message msg) {
|
|
||||||
final MusicService service = mService.get();
|
|
||||||
if (msg.what == SAVE_QUEUES) {
|
|
||||||
service.saveQueuesImpl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class PlaybackHandler extends Handler {
|
public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) {
|
||||||
@NonNull
|
this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus;
|
||||||
private final WeakReference<MusicService> mService;
|
|
||||||
private float currentDuckVolume = 1.0f;
|
|
||||||
|
|
||||||
PlaybackHandler(final MusicService service, @NonNull final Looper looper) {
|
|
||||||
super(looper);
|
|
||||||
mService = new WeakReference<>(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleMessage(@NonNull final Message msg) {
|
|
||||||
final MusicService service = mService.get();
|
|
||||||
if (service == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (msg.what) {
|
|
||||||
case DUCK:
|
|
||||||
if (PreferenceUtil.getInstance().audioDucking()) {
|
|
||||||
currentDuckVolume -= .05f;
|
|
||||||
if (currentDuckVolume > .2f) {
|
|
||||||
sendEmptyMessageDelayed(DUCK, 10);
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = .2f;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = 1f;
|
|
||||||
}
|
|
||||||
service.playback.setVolume(currentDuckVolume);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UNDUCK:
|
|
||||||
if (PreferenceUtil.getInstance().audioDucking()) {
|
|
||||||
currentDuckVolume += .03f;
|
|
||||||
if (currentDuckVolume < 1f) {
|
|
||||||
sendEmptyMessageDelayed(UNDUCK, 10);
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = 1f;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentDuckVolume = 1f;
|
|
||||||
}
|
|
||||||
service.playback.setVolume(currentDuckVolume);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TRACK_WENT_TO_NEXT:
|
|
||||||
if (service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) {
|
|
||||||
service.pause();
|
|
||||||
service.seek(0);
|
|
||||||
} else {
|
|
||||||
service.position = service.nextPosition;
|
|
||||||
service.prepareNextImpl();
|
|
||||||
service.notifyChange(META_CHANGED);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TRACK_ENDED:
|
|
||||||
// if there is a timer finished, don't continue
|
|
||||||
if (service.pendingQuit ||
|
|
||||||
service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) {
|
|
||||||
service.notifyChange(PLAY_STATE_CHANGED);
|
|
||||||
service.seek(0);
|
|
||||||
if (service.pendingQuit) {
|
|
||||||
service.pendingQuit = false;
|
|
||||||
service.quit();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
service.playNextSong(false);
|
|
||||||
}
|
|
||||||
sendEmptyMessage(RELEASE_WAKELOCK);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RELEASE_WAKELOCK:
|
|
||||||
service.releaseWakeLock();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PLAY_SONG:
|
|
||||||
service.playSongAtImpl(msg.arg1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SET_POSITION:
|
|
||||||
service.openTrackAndPrepareNextAt(msg.arg1);
|
|
||||||
service.notifyChange(PLAY_STATE_CHANGED);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PREPARE_NEXT:
|
|
||||||
service.prepareNextImpl();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESTORE_QUEUES:
|
|
||||||
service.restoreQueuesAndPositionIfNecessary();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FOCUS_CHANGE:
|
|
||||||
switch (msg.arg1) {
|
|
||||||
case AudioManager.AUDIOFOCUS_GAIN:
|
|
||||||
if (!service.isPlaying() && service.pausedByTransientLossOfFocus) {
|
|
||||||
service.play();
|
|
||||||
service.pausedByTransientLossOfFocus = false;
|
|
||||||
}
|
|
||||||
removeMessages(DUCK);
|
|
||||||
sendEmptyMessage(UNDUCK);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS:
|
|
||||||
// Lost focus for an unbounded amount of time: stop playback and release media playback
|
|
||||||
service.pause();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
|
||||||
// Lost focus for a short time, but we have to stop
|
|
||||||
// playback. We don't release the media playback because playback
|
|
||||||
// is likely to resume
|
|
||||||
boolean wasPlaying = service.isPlaying();
|
|
||||||
service.pause();
|
|
||||||
service.pausedByTransientLossOfFocus = wasPlaying;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
|
||||||
// Lost focus for a short time, but it's ok to keep playing
|
|
||||||
// at an attenuated level
|
|
||||||
removeMessages(UNDUCK);
|
|
||||||
sendEmptyMessage(DUCK);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SongPlayCountHelper {
|
|
||||||
public static final String TAG = SongPlayCountHelper.class.getSimpleName();
|
|
||||||
|
|
||||||
private StopWatch stopWatch = new StopWatch();
|
|
||||||
private Song song = Song.getEmptySong();
|
|
||||||
|
|
||||||
public Song getSong() {
|
|
||||||
return song;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean shouldBumpPlayCount() {
|
|
||||||
return song.getDuration() * 0.5d < stopWatch.getElapsedTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifySongChanged(Song song) {
|
|
||||||
synchronized (this) {
|
|
||||||
stopWatch.reset();
|
|
||||||
this.song = song;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyPlayStateChanged(boolean isPlaying) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (isPlaying) {
|
|
||||||
stopWatch.start();
|
|
||||||
} else {
|
|
||||||
stopWatch.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MusicBinder extends Binder {
|
public class MusicBinder extends Binder {
|
||||||
|
@ -1455,52 +1267,4 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
||||||
return MusicService.this;
|
return MusicService.this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MediaStoreObserver extends ContentObserver implements Runnable {
|
|
||||||
// milliseconds to delay before calling refresh to aggregate events
|
|
||||||
private static final long REFRESH_DELAY = 500;
|
|
||||||
private Handler mHandler;
|
|
||||||
|
|
||||||
MediaStoreObserver(Handler handler) {
|
|
||||||
super(handler);
|
|
||||||
mHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChange(boolean selfChange) {
|
|
||||||
// if a change is detected, remove any scheduled callback
|
|
||||||
// then post a new one. This is intended to prevent closely
|
|
||||||
// spaced events from generating multiple refresh calls
|
|
||||||
mHandler.removeCallbacks(this);
|
|
||||||
mHandler.postDelayed(this, REFRESH_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// actually call refresh when the delayed callback fires
|
|
||||||
// do not send a sticky broadcast here
|
|
||||||
handleAndSendChangeInternal(MEDIA_STORE_CHANGED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ThrottledSeekHandler implements Runnable {
|
|
||||||
// milliseconds to throttle before calling run() to aggregate events
|
|
||||||
private static final long THROTTLE = 500;
|
|
||||||
private Handler mHandler;
|
|
||||||
|
|
||||||
ThrottledSeekHandler(Handler handler) {
|
|
||||||
mHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifySeek() {
|
|
||||||
mHandler.removeCallbacks(this);
|
|
||||||
mHandler.postDelayed(this, THROTTLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
savePositionInTrack();
|
|
||||||
sendPublicIntent(PLAY_STATE_CHANGED); // for musixmatch synced lyrics
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||||
|
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.DUCK;
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.META_CHANGED;
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.PLAY_STATE_CHANGED;
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.REPEAT_MODE_NONE;
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.TRACK_ENDED;
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.TRACK_WENT_TO_NEXT;
|
||||||
|
|
||||||
|
class PlaybackHandler extends Handler {
|
||||||
|
@NonNull
|
||||||
|
private final WeakReference<MusicService> mService;
|
||||||
|
private float currentDuckVolume = 1.0f;
|
||||||
|
|
||||||
|
PlaybackHandler(final MusicService service, @NonNull final Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
mService = new WeakReference<>(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(@NonNull final Message msg) {
|
||||||
|
final MusicService service = mService.get();
|
||||||
|
if (service == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (msg.what) {
|
||||||
|
case MusicService.DUCK:
|
||||||
|
if (PreferenceUtil.getInstance().audioDucking()) {
|
||||||
|
currentDuckVolume -= .05f;
|
||||||
|
if (currentDuckVolume > .2f) {
|
||||||
|
sendEmptyMessageDelayed(DUCK, 10);
|
||||||
|
} else {
|
||||||
|
currentDuckVolume = .2f;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentDuckVolume = 1f;
|
||||||
|
}
|
||||||
|
service.playback.setVolume(currentDuckVolume);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.UNDUCK:
|
||||||
|
if (PreferenceUtil.getInstance().audioDucking()) {
|
||||||
|
currentDuckVolume += .03f;
|
||||||
|
if (currentDuckVolume < 1f) {
|
||||||
|
sendEmptyMessageDelayed(MusicService.UNDUCK, 10);
|
||||||
|
} else {
|
||||||
|
currentDuckVolume = 1f;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentDuckVolume = 1f;
|
||||||
|
}
|
||||||
|
service.playback.setVolume(currentDuckVolume);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TRACK_WENT_TO_NEXT:
|
||||||
|
if (service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) {
|
||||||
|
service.pause();
|
||||||
|
service.seek(0);
|
||||||
|
} else {
|
||||||
|
service.position = service.nextPosition;
|
||||||
|
service.prepareNextImpl();
|
||||||
|
service.notifyChange(META_CHANGED);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TRACK_ENDED:
|
||||||
|
// if there is a timer finished, don't continue
|
||||||
|
if (service.pendingQuit ||
|
||||||
|
service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) {
|
||||||
|
service.notifyChange(PLAY_STATE_CHANGED);
|
||||||
|
service.seek(0);
|
||||||
|
if (service.pendingQuit) {
|
||||||
|
service.pendingQuit = false;
|
||||||
|
service.quit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
service.playNextSong(false);
|
||||||
|
}
|
||||||
|
sendEmptyMessage(MusicService.RELEASE_WAKELOCK);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.RELEASE_WAKELOCK:
|
||||||
|
service.releaseWakeLock();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.PLAY_SONG:
|
||||||
|
service.playSongAtImpl(msg.arg1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.SET_POSITION:
|
||||||
|
service.openTrackAndPrepareNextAt(msg.arg1);
|
||||||
|
service.notifyChange(PLAY_STATE_CHANGED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.PREPARE_NEXT:
|
||||||
|
service.prepareNextImpl();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.RESTORE_QUEUES:
|
||||||
|
service.restoreQueuesAndPositionIfNecessary();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MusicService.FOCUS_CHANGE:
|
||||||
|
switch (msg.arg1) {
|
||||||
|
case AudioManager.AUDIOFOCUS_GAIN:
|
||||||
|
if (!service.isPlaying() && service.isPausedByTransientLossOfFocus()) {
|
||||||
|
service.play();
|
||||||
|
service.setPausedByTransientLossOfFocus(false);
|
||||||
|
}
|
||||||
|
removeMessages(DUCK);
|
||||||
|
sendEmptyMessage(MusicService.UNDUCK);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioManager.AUDIOFOCUS_LOSS:
|
||||||
|
// Lost focus for an unbounded amount of time: stop playback and release media playback
|
||||||
|
service.pause();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||||
|
// Lost focus for a short time, but we have to stop
|
||||||
|
// playback. We don't release the media playback because playback
|
||||||
|
// is likely to resume
|
||||||
|
boolean wasPlaying = service.isPlaying();
|
||||||
|
service.pause();
|
||||||
|
service.setPausedByTransientLossOfFocus(wasPlaying);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||||
|
// Lost focus for a short time, but it's ok to keep playing
|
||||||
|
// at an attenuated level
|
||||||
|
removeMessages(MusicService.UNDUCK);
|
||||||
|
sendEmptyMessage(DUCK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.SAVE_QUEUES;
|
||||||
|
|
||||||
|
class QueueSaveHandler extends Handler {
|
||||||
|
@NonNull
|
||||||
|
private final WeakReference<MusicService> mService;
|
||||||
|
|
||||||
|
QueueSaveHandler(final MusicService service, @NonNull final Looper looper) {
|
||||||
|
super(looper);
|
||||||
|
mService = new WeakReference<>(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(@NonNull Message msg) {
|
||||||
|
final MusicService service = mService.get();
|
||||||
|
if (msg.what == SAVE_QUEUES) {
|
||||||
|
service.saveQueuesImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
|
import code.name.monkey.retromusic.helper.StopWatch;
|
||||||
|
import code.name.monkey.retromusic.model.Song;
|
||||||
|
|
||||||
|
public class SongPlayCountHelper {
|
||||||
|
public static final String TAG = SongPlayCountHelper.class.getSimpleName();
|
||||||
|
|
||||||
|
private StopWatch stopWatch = new StopWatch();
|
||||||
|
private Song song = Song.getEmptySong();
|
||||||
|
|
||||||
|
public Song getSong() {
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shouldBumpPlayCount() {
|
||||||
|
return song.getDuration() * 0.5d < stopWatch.getElapsedTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifySongChanged(Song song) {
|
||||||
|
synchronized (this) {
|
||||||
|
stopWatch.reset();
|
||||||
|
this.song = song;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyPlayStateChanged(boolean isPlaying) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (isPlaying) {
|
||||||
|
stopWatch.start();
|
||||||
|
} else {
|
||||||
|
stopWatch.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
|
import static code.name.monkey.retromusic.service.MusicService.PLAY_STATE_CHANGED;
|
||||||
|
|
||||||
|
public class ThrottledSeekHandler implements Runnable {
|
||||||
|
// milliseconds to throttle before calling run() to aggregate events
|
||||||
|
private static final long THROTTLE = 500;
|
||||||
|
private final MusicService musicService;
|
||||||
|
private Handler handler;
|
||||||
|
|
||||||
|
ThrottledSeekHandler(MusicService musicService, Handler handler) {
|
||||||
|
this.musicService = musicService;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifySeek() {
|
||||||
|
handler.removeCallbacks(this);
|
||||||
|
handler.postDelayed(this, THROTTLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
musicService.savePositionInTrack();
|
||||||
|
musicService.sendPublicIntent(PLAY_STATE_CHANGED); // for musixmatch synced lyrics
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,12 +24,13 @@ import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.service.media.MediaBrowserService;
|
import android.service.media.MediaBrowserService;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
|
||||||
import code.name.monkey.retromusic.loaders.AlbumLoader;
|
import code.name.monkey.retromusic.loaders.AlbumLoader;
|
||||||
|
@ -194,7 +195,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
} else {
|
} else {
|
||||||
switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) {
|
switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) {
|
||||||
case TYPE_ARTIST:
|
case TYPE_ARTIST:
|
||||||
List<Artist> artistList = ArtistLoader.INSTANCE.getAllArtists(mContext).blockingFirst();
|
List<Artist> artistList = ArtistLoader.INSTANCE.getAllArtists(mContext) ;
|
||||||
for (Artist artist : artistList) {
|
for (Artist artist : artistList) {
|
||||||
String albumNmber = String.format("%d %s", artist.getAlbums().size(), artist.getAlbums().size() > 1 ? "Albums" : "Album");
|
String albumNmber = String.format("%d %s", artist.getAlbums().size(), artist.getAlbums().size() > 1 ? "Albums" : "Album");
|
||||||
String songCount = String.format("%d %s", artist.getSongs().size(), artist.getSongs().size() > 1 ? "Songs" : "Song");
|
String songCount = String.format("%d %s", artist.getSongs().size(), artist.getSongs().size() > 1 ? "Songs" : "Song");
|
||||||
|
@ -215,7 +216,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
Uri.parse("android.resource://code.name.monkey.retromusic/drawable/default_artist_art"),
|
Uri.parse("android.resource://code.name.monkey.retromusic/drawable/default_artist_art"),
|
||||||
MediaBrowser.MediaItem.FLAG_BROWSABLE);
|
MediaBrowser.MediaItem.FLAG_BROWSABLE);
|
||||||
|
|
||||||
List<Album> artistAlbums = ArtistLoader.INSTANCE.getArtist(mContext, Integer.parseInt(parentId.substring(1))).blockingFirst().getAlbums(); //ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1)));
|
List<Album> artistAlbums = ArtistLoader.INSTANCE.getArtist(mContext, Integer.parseInt(parentId.substring(1))).getAlbums(); //ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1)));
|
||||||
for (Album album : artistAlbums) {
|
for (Album album : artistAlbums) {
|
||||||
String songCount = String.format("%d %s", album.getSongs().size(), album.getSongs().size() > 1 ? "Songs" : "Song");
|
String songCount = String.format("%d %s", album.getSongs().size(), album.getSongs().size() > 1 ? "Songs" : "Song");
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
|
@ -227,7 +228,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_ALBUM:
|
case TYPE_ALBUM:
|
||||||
List<Album> albumList = AlbumLoader.Companion.getAllAlbums(mContext).blockingFirst();
|
List<Album> albumList = AlbumLoader.INSTANCE.getAllAlbums(mContext);
|
||||||
for (Album album : albumList) {
|
for (Album album : albumList) {
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.getId()),
|
Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.getId()),
|
||||||
|
@ -238,7 +239,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_SONG:
|
case TYPE_SONG:
|
||||||
List<Song> songList = SongLoader.INSTANCE.getAllSongs(mContext).blockingFirst();
|
List<Song> songList = SongLoader.INSTANCE.getAllSongs(mContext);
|
||||||
for (Song song : songList) {
|
for (Song song : songList) {
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
String.valueOf(song.getId()),
|
String.valueOf(song.getId()),
|
||||||
|
@ -250,7 +251,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TYPE_ALBUM_SONGS:
|
case TYPE_ALBUM_SONGS:
|
||||||
List<Song> albumSongList = AlbumLoader.Companion.getAlbum(mContext, Integer.parseInt(parentId.substring(1))).blockingFirst().getSongs();
|
List<Song> albumSongList = AlbumLoader.INSTANCE.getAlbum(mContext, Integer.parseInt(parentId.substring(1))).getSongs();
|
||||||
for (Song song : albumSongList) {
|
for (Song song : albumSongList) {
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
String.valueOf(song.getId()),
|
String.valueOf(song.getId()),
|
||||||
|
@ -261,7 +262,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_ARTIST_ALL_SONGS:
|
case TYPE_ARTIST_ALL_SONGS:
|
||||||
List<Song> artistSongs = ArtistLoader.INSTANCE.getArtist(mContext, Integer.parseInt(parentId.substring(1))).blockingFirst().getSongs();
|
List<Song> artistSongs = ArtistLoader.INSTANCE.getArtist(mContext, Integer.parseInt(parentId.substring(1))).getSongs();
|
||||||
for (Song song : artistSongs) {
|
for (Song song : artistSongs) {
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
String.valueOf(song.getId()),
|
String.valueOf(song.getId()),
|
||||||
|
@ -272,9 +273,9 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_PLAYLIST:
|
case TYPE_PLAYLIST:
|
||||||
List<Playlist> playlistList = PlaylistLoader.INSTANCE.getAllPlaylists(mContext).blockingFirst();
|
List<Playlist> playlistList = PlaylistLoader.INSTANCE.getAllPlaylists(mContext);
|
||||||
for (Playlist playlist : playlistList) {
|
for (Playlist playlist : playlistList) {
|
||||||
int size = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(mContext, playlist).blockingFirst().size();
|
int size = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(mContext, playlist).size();
|
||||||
String songCount = String.format("%d %s", size, size > 1 ? "Songs" : "Song");
|
String songCount = String.format("%d %s", size, size > 1 ? "Songs" : "Song");
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id),
|
Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id),
|
||||||
|
@ -285,7 +286,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_PLAYLIST_ALL_SONGS:
|
case TYPE_PLAYLIST_ALL_SONGS:
|
||||||
List<Song> playlistSongs = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(mContext, Integer.parseInt(parentId.substring(1))).blockingFirst();
|
List<Song> playlistSongs = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(mContext, Integer.parseInt(parentId.substring(1)));
|
||||||
for (Song song : playlistSongs) {
|
for (Song song : playlistSongs) {
|
||||||
fillMediaItems(mediaItems,
|
fillMediaItems(mediaItems,
|
||||||
String.valueOf(song.getId()),
|
String.valueOf(song.getId()),
|
||||||
|
@ -326,7 +327,7 @@ public class WearBrowserService extends MediaBrowserService {
|
||||||
long songId = Long.parseLong(mediaId);
|
long songId = Long.parseLong(mediaId);
|
||||||
setSessionActive();
|
setSessionActive();
|
||||||
ArrayList<Song> songs = new ArrayList<>();
|
ArrayList<Song> songs = new ArrayList<>();
|
||||||
songs.add(SongLoader.INSTANCE.getSong(mContext, Integer.parseInt(mediaId)).blockingFirst());
|
songs.add(SongLoader.INSTANCE.getSong(mContext, Integer.parseInt(mediaId)));
|
||||||
MusicPlayerRemote.INSTANCE.openQueue(songs, 0, true);
|
MusicPlayerRemote.INSTANCE.openQueue(songs, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,6 @@ import android.widget.RemoteViews
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_QUIT
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_REWIND
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_SKIP
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget.Companion.createBitmap
|
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget.Companion.createBitmap
|
||||||
|
@ -40,6 +36,7 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.RetroColorUtil
|
import code.name.monkey.retromusic.util.RetroColorUtil
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
|
|
|
@ -24,17 +24,15 @@ import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_QUIT
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_REWIND
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_SKIP
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.glide.GlideApp
|
import code.name.monkey.retromusic.glide.GlideApp
|
||||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
|
import code.name.monkey.retromusic.util.MusicUtil
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.RetroColorUtil
|
import code.name.monkey.retromusic.util.RetroColorUtil
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
|
@ -48,12 +46,18 @@ class PlayingNotificationImpl24 : PlayingNotification() {
|
||||||
|
|
||||||
val song = service.currentSong
|
val song = service.currentSong
|
||||||
val isPlaying = service.isPlaying
|
val isPlaying = service.isPlaying
|
||||||
|
val isFavorite = MusicUtil.isFavorite(service, song)
|
||||||
val playButtonResId = if (isPlaying)
|
val playButtonResId = if (isPlaying)
|
||||||
R.drawable.ic_pause_white_24dp
|
R.drawable.ic_pause_white_24dp
|
||||||
else
|
else
|
||||||
R.drawable.ic_play_arrow_white_32dp
|
R.drawable.ic_play_arrow_white_32dp
|
||||||
|
|
||||||
|
val favoriteResId = if (isFavorite)
|
||||||
|
R.drawable.ic_favorite_white_24dp
|
||||||
|
else
|
||||||
|
R.drawable.ic_favorite_border_white_24dp
|
||||||
|
|
||||||
|
|
||||||
val action = Intent(service, MainActivity::class.java)
|
val action = Intent(service, MainActivity::class.java)
|
||||||
action.putExtra("expand", true)
|
action.putExtra("expand", true)
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
@ -93,6 +97,12 @@ class PlayingNotificationImpl24 : PlayingNotification() {
|
||||||
if (bitmapFinal == null) {
|
if (bitmapFinal == null) {
|
||||||
bitmapFinal = BitmapFactory.decodeResource(service.resources, R.drawable.default_album_art)
|
bitmapFinal = BitmapFactory.decodeResource(service.resources, R.drawable.default_album_art)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val toggleFavorite = NotificationCompat.Action(favoriteResId,
|
||||||
|
service.getString(R.string.action_toggle_favorite),
|
||||||
|
retrievePlaybackAction(TOGGLE_FAVORITE))
|
||||||
|
|
||||||
val playPauseAction = NotificationCompat.Action(
|
val playPauseAction = NotificationCompat.Action(
|
||||||
playButtonResId,
|
playButtonResId,
|
||||||
service.getString(R.string.action_play_pause),
|
service.getString(R.string.action_play_pause),
|
||||||
|
@ -114,7 +124,7 @@ class PlayingNotificationImpl24 : PlayingNotification() {
|
||||||
retrievePlaybackAction(ACTION_SKIP))
|
retrievePlaybackAction(ACTION_SKIP))
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(service,
|
val builder = NotificationCompat.Builder(service,
|
||||||
PlayingNotification.NOTIFICATION_CHANNEL_ID)
|
NOTIFICATION_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setLargeIcon(bitmapFinal)
|
.setLargeIcon(bitmapFinal)
|
||||||
.setContentIntent(clickIntent)
|
.setContentIntent(clickIntent)
|
||||||
|
|
|
@ -25,10 +25,6 @@ import android.widget.RemoteViews
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_QUIT
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_REWIND
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_SKIP
|
|
||||||
import code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE
|
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.glide.GlideApp
|
import code.name.monkey.retromusic.glide.GlideApp
|
||||||
|
@ -37,6 +33,7 @@ import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.service.MusicService
|
import code.name.monkey.retromusic.service.MusicService
|
||||||
|
import code.name.monkey.retromusic.service.MusicService.*
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import code.name.monkey.retromusic.util.RetroUtil.createBitmap
|
import code.name.monkey.retromusic.util.RetroUtil.createBitmap
|
||||||
|
@ -155,14 +152,14 @@ class PlayingNotificationOreo : PlayingNotification() {
|
||||||
val primary = MaterialValueHelper.getPrimaryTextColor(service, dark)
|
val primary = MaterialValueHelper.getPrimaryTextColor(service, dark)
|
||||||
val secondary = MaterialValueHelper.getSecondaryTextColor(service, dark)
|
val secondary = MaterialValueHelper.getSecondaryTextColor(service, dark)
|
||||||
|
|
||||||
val close = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_close_white_24dp, primary)!!, PlayingNotification.NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
val close = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_close_white_24dp, primary)!!, NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
||||||
val prev = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, primary)!!, PlayingNotification.NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
val prev = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, primary)!!, NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
||||||
val next = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, primary)!!, PlayingNotification.NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
val next = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, primary)!!, NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
||||||
val playPause = createBitmap(RetroUtil.getTintedVectorDrawable(service,
|
val playPause = createBitmap(RetroUtil.getTintedVectorDrawable(service,
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
R.drawable.ic_pause_white_24dp
|
R.drawable.ic_pause_white_24dp
|
||||||
else
|
else
|
||||||
R.drawable.ic_play_arrow_white_32dp, primary)!!, PlayingNotification.NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
R.drawable.ic_play_arrow_white_32dp, primary)!!, NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
||||||
|
|
||||||
notificationLayout.setTextColor(R.id.title, primary)
|
notificationLayout.setTextColor(R.id.title, primary)
|
||||||
notificationLayout.setTextColor(R.id.subtitle, secondary)
|
notificationLayout.setTextColor(R.id.subtitle, secondary)
|
||||||
|
|
|
@ -20,6 +20,9 @@ import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -34,12 +37,9 @@ import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||||
import code.name.monkey.retromusic.loaders.SortedCursor;
|
import code.name.monkey.retromusic.loaders.SortedCursor;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import io.reactivex.Observable;
|
|
||||||
|
|
||||||
|
|
||||||
public final class FileUtil {
|
public final class FileUtil {
|
||||||
|
@ -57,9 +57,10 @@ public final class FileUtil {
|
||||||
stream.close();
|
stream.close();
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Observable<ArrayList<Song>> matchFilesWithMediaStore(@NonNull Context context,
|
public static ArrayList<Song> matchFilesWithMediaStore(@NonNull Context context,
|
||||||
@Nullable List<File> files) {
|
@Nullable List<File> files) {
|
||||||
return SongLoader.INSTANCE.getSongs(makeSongCursor(context, files));
|
return SongLoader.INSTANCE.getSongs(makeSongCursor(context, files));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.util;
|
package code.name.monkey.retromusic.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
@ -21,13 +23,22 @@ import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.media.ExifInterface;
|
import android.media.ExifInterface;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import code.name.monkey.appthemehelper.util.TintHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub :
|
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub :
|
||||||
* https://github.com/zetbaitsu
|
* https://github.com/zetbaitsu
|
||||||
|
@ -57,6 +68,37 @@ public class ImageUtil {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Bitmap createBitmap(Drawable drawable) {
|
||||||
|
return createBitmap(drawable, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) {
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas c = new Canvas(bitmap);
|
||||||
|
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
drawable.draw(c);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getTintedVectorDrawable(@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme, @ColorInt int color) {
|
||||||
|
return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id, @ColorInt int color) {
|
||||||
|
return TintHelper.createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getVectorDrawable(@NonNull Context context, @DrawableRes int id) {
|
||||||
|
return getVectorDrawable(context.getResources(), id, context.getTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId, @Nullable Resources.Theme theme) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
return res.getDrawable(resId, theme);
|
||||||
|
}
|
||||||
|
return VectorDrawableCompat.create(res, resId, theme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure that {@code mTempBuffer} has at least length {@code size}.
|
* Makes sure that {@code mTempBuffer} has at least length {@code size}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -51,7 +51,6 @@ import code.name.monkey.retromusic.model.Genre;
|
||||||
import code.name.monkey.retromusic.model.Playlist;
|
import code.name.monkey.retromusic.model.Playlist;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
|
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
|
||||||
import io.reactivex.Observable;
|
|
||||||
|
|
||||||
|
|
||||||
public class MusicUtil {
|
public class MusicUtil {
|
||||||
|
@ -303,7 +302,7 @@ public class MusicUtil {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
while (!cursor.isAfterLast()) {
|
while (!cursor.isAfterLast()) {
|
||||||
final int id = cursor.getInt(0);
|
final int id = cursor.getInt(0);
|
||||||
final Song song = SongLoader.INSTANCE.getSong(activity, id).blockingFirst();
|
final Song song = SongLoader.INSTANCE.getSong(activity, id);
|
||||||
MusicPlayerRemote.INSTANCE.removeFromQueue(song);
|
MusicPlayerRemote.INSTANCE.removeFromQueue(song);
|
||||||
cursor.moveToNext();
|
cursor.moveToNext();
|
||||||
}
|
}
|
||||||
|
@ -404,9 +403,9 @@ public class MusicUtil {
|
||||||
|
|
||||||
public static void toggleFavorite(@NonNull final Context context, @NonNull final Song song) {
|
public static void toggleFavorite(@NonNull final Context context, @NonNull final Song song) {
|
||||||
if (isFavorite(context, song)) {
|
if (isFavorite(context, song)) {
|
||||||
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).blockingFirst().id);
|
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).id);
|
||||||
} else {
|
} else {
|
||||||
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).blockingFirst().id,
|
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).id,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -416,18 +415,18 @@ public class MusicUtil {
|
||||||
return playlist.name != null && playlist.name.equals(context.getString(R.string.favorites));
|
return playlist.name != null && playlist.name.equals(context.getString(R.string.favorites));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Observable<Playlist> getFavoritesPlaylist(@NonNull final Context context) {
|
private static Playlist getFavoritesPlaylist(@NonNull final Context context) {
|
||||||
return PlaylistLoader.INSTANCE.getPlaylist(context, context.getString(R.string.favorites));
|
return PlaylistLoader.INSTANCE.getPlaylist(context, context.getString(R.string.favorites));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Observable<Playlist> getOrCreateFavoritesPlaylist(@NonNull final Context context) {
|
private static Playlist getOrCreateFavoritesPlaylist(@NonNull final Context context) {
|
||||||
return PlaylistLoader.INSTANCE.getPlaylist(context,
|
return PlaylistLoader.INSTANCE.getPlaylist(context,
|
||||||
PlaylistsUtil.createPlaylist(context, context.getString(R.string.favorites)));
|
PlaylistsUtil.createPlaylist(context, context.getString(R.string.favorites)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFavorite(@NonNull final Context context, @NonNull final Song song) {
|
public static boolean isFavorite(@NonNull final Context context, @NonNull final Song song) {
|
||||||
return PlaylistsUtil
|
return PlaylistsUtil
|
||||||
.doPlaylistContains(context, getFavoritesPlaylist(context).blockingFirst().id, song.getId());
|
.doPlaylistContains(context, getFavoritesPlaylist(context).id, song.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isArtistNameUnknown(@Nullable String artistName) {
|
public static boolean isArtistNameUnknown(@Nullable String artistName) {
|
||||||
|
@ -475,6 +474,15 @@ public class MusicUtil {
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int indexOfSongInList(List<Song> songs, int songId) {
|
||||||
|
for (int i = 0; i < songs.size(); i++) {
|
||||||
|
if (songs.get(i).getId() == songId) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String getYearString(int year) {
|
public static String getYearString(int year) {
|
||||||
return year > 0 ? String.valueOf(year) : "-";
|
return year > 0 ? String.valueOf(year) : "-";
|
||||||
|
|
|
@ -0,0 +1,342 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Hemanth Savarala.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU General Public License v3
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
package code.name.monkey.retromusic.util
|
||||||
|
|
||||||
|
import android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE
|
||||||
|
import android.Manifest.permission.MEDIA_CONTENT_CONTROL
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.XmlResourceParser
|
||||||
|
import android.os.Process
|
||||||
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.XmlRes
|
||||||
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
|
import code.name.monkey.retromusic.BuildConfig
|
||||||
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the calling package is authorized to browse a [MediaBrowserServiceCompat].
|
||||||
|
*
|
||||||
|
* The list of allowed signing certificates and their corresponding package names is defined in
|
||||||
|
* res/xml/allowed_media_browser_callers.xml.
|
||||||
|
*
|
||||||
|
* If you want to add a new caller to allowed_media_browser_callers.xml and you don't know
|
||||||
|
* its signature, this class will print to logcat (INFO level) a message with the proper
|
||||||
|
* xml tags to add to allow the caller.
|
||||||
|
*
|
||||||
|
* For more information, see res/xml/allowed_media_browser_callers.xml.
|
||||||
|
*/
|
||||||
|
class PackageValidator(context: Context, @XmlRes xmlResId: Int) {
|
||||||
|
private val context: Context
|
||||||
|
private val packageManager: PackageManager
|
||||||
|
|
||||||
|
private val certificateWhitelist: Map<String, KnownCallerInfo>
|
||||||
|
private val platformSignature: String
|
||||||
|
|
||||||
|
private val callerChecked = mutableMapOf<String, Pair<Int, Boolean>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val parser = context.resources.getXml(xmlResId)
|
||||||
|
this.context = context.applicationContext
|
||||||
|
this.packageManager = this.context.packageManager
|
||||||
|
|
||||||
|
certificateWhitelist = buildCertificateWhitelist(parser)
|
||||||
|
platformSignature = getSystemSignature()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the caller attempting to connect to a [MediaBrowserServiceCompat] is known.
|
||||||
|
* See [MusicService.onGetRoot] for where this is utilized.
|
||||||
|
*
|
||||||
|
* @param callingPackage The package name of the caller.
|
||||||
|
* @param callingUid The user id of the caller.
|
||||||
|
* @return `true` if the caller is known, `false` otherwise.
|
||||||
|
*/
|
||||||
|
fun isKnownCaller(callingPackage: String, callingUid: Int): Boolean {
|
||||||
|
// If the caller has already been checked, return the previous result here.
|
||||||
|
val (checkedUid, checkResult) = callerChecked[callingPackage] ?: Pair(0, false)
|
||||||
|
if (checkedUid == callingUid) {
|
||||||
|
return checkResult
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because some of these checks can be slow, we save the results in [callerChecked] after
|
||||||
|
* this code is run.
|
||||||
|
*
|
||||||
|
* In particular, there's little reason to recompute the calling package's certificate
|
||||||
|
* signature (SHA-256) each call.
|
||||||
|
*
|
||||||
|
* This is safe to do as we know the UID matches the package's UID (from the check above),
|
||||||
|
* and app UIDs are set at install time. Additionally, a package name + UID is guaranteed to
|
||||||
|
* be constant until a reboot. (After a reboot then a previously assigned UID could be
|
||||||
|
* reassigned.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Build the caller info for the rest of the checks here.
|
||||||
|
val callerPackageInfo = buildCallerInfo(callingPackage)
|
||||||
|
?: throw IllegalStateException("Caller wasn't found in the system?")
|
||||||
|
|
||||||
|
// Verify that things aren't ... broken. (This test should always pass.)
|
||||||
|
if (callerPackageInfo.uid != callingUid) {
|
||||||
|
throw IllegalStateException("Caller's package UID doesn't match caller's actual UID?")
|
||||||
|
}
|
||||||
|
|
||||||
|
val callerSignature = callerPackageInfo.signature
|
||||||
|
val isPackageInWhitelist = certificateWhitelist[callingPackage]?.signatures?.first {
|
||||||
|
it.signature == callerSignature
|
||||||
|
} != null
|
||||||
|
|
||||||
|
val isCallerKnown = when {
|
||||||
|
// If it's our own app making the call, allow it.
|
||||||
|
callingUid == Process.myUid() -> true
|
||||||
|
// If it's one of the apps on the whitelist, allow it.
|
||||||
|
isPackageInWhitelist -> true
|
||||||
|
// If the system is making the call, allow it.
|
||||||
|
callingUid == Process.SYSTEM_UID -> true
|
||||||
|
// If the app was signed by the same certificate as the platform itself, also allow it.
|
||||||
|
callerSignature == platformSignature -> true
|
||||||
|
/**
|
||||||
|
* [MEDIA_CONTENT_CONTROL] permission is only available to system applications, and
|
||||||
|
* while it isn't required to allow these apps to connect to a
|
||||||
|
* [MediaBrowserServiceCompat], allowing this ensures optimal compatability with apps
|
||||||
|
* such as Android TV and the Google Assistant.
|
||||||
|
*/
|
||||||
|
callerPackageInfo.permissions.contains(MEDIA_CONTENT_CONTROL) -> true
|
||||||
|
/**
|
||||||
|
* This last permission can be specifically granted to apps, and, in addition to
|
||||||
|
* allowing them to retrieve notifications, it also allows them to connect to an
|
||||||
|
* active [MediaSessionCompat].
|
||||||
|
* As with the above, it's not required to allow apps holding this permission to
|
||||||
|
* connect to your [MediaBrowserServiceCompat], but it does allow easy comparability
|
||||||
|
* with apps such as Wear OS.
|
||||||
|
*/
|
||||||
|
callerPackageInfo.permissions.contains(BIND_NOTIFICATION_LISTENER_SERVICE) -> true
|
||||||
|
// If none of the pervious checks succeeded, then the caller is unrecognized.
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCallerKnown) {
|
||||||
|
logUnknownCaller(callerPackageInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save our work for next time.
|
||||||
|
callerChecked[callingPackage] = Pair(callingUid, isCallerKnown)
|
||||||
|
return isCallerKnown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs an info level message with details of how to add a caller to the allowed callers list
|
||||||
|
* when the app is debuggable.
|
||||||
|
*/
|
||||||
|
private fun logUnknownCaller(callerPackageInfo: CallerPackageInfo) {
|
||||||
|
if (BuildConfig.DEBUG && callerPackageInfo.signature != null) {
|
||||||
|
Log.i(TAG, "PackageValidator call" + callerPackageInfo.name + callerPackageInfo.packageName + callerPackageInfo.signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a [CallerPackageInfo] for a given package that can be used for all the
|
||||||
|
* various checks that are performed before allowing an app to connect to a
|
||||||
|
* [MediaBrowserServiceCompat].
|
||||||
|
*/
|
||||||
|
private fun buildCallerInfo(callingPackage: String): CallerPackageInfo? {
|
||||||
|
val packageInfo = getPackageInfo(callingPackage) ?: return null
|
||||||
|
|
||||||
|
val appName = packageInfo.applicationInfo.loadLabel(packageManager).toString()
|
||||||
|
val uid = packageInfo.applicationInfo.uid
|
||||||
|
val signature = getSignature(packageInfo)
|
||||||
|
|
||||||
|
val requestedPermissions = packageInfo.requestedPermissions
|
||||||
|
val permissionFlags = packageInfo.requestedPermissionsFlags
|
||||||
|
val activePermissions = mutableSetOf<String>()
|
||||||
|
requestedPermissions?.forEachIndexed { index, permission ->
|
||||||
|
if (permissionFlags[index] and REQUESTED_PERMISSION_GRANTED != 0) {
|
||||||
|
activePermissions += permission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CallerPackageInfo(appName, callingPackage, uid, signature, activePermissions.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the [PackageInfo] for a package name.
|
||||||
|
* This requests both the signatures (for checking if an app is on the whitelist) and
|
||||||
|
* the app's permissions, which allow for more flexibility in the whitelist.
|
||||||
|
*
|
||||||
|
* @return [PackageInfo] for the package name or null if it's not found.
|
||||||
|
*/
|
||||||
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
|
private fun getPackageInfo(callingPackage: String): PackageInfo? =
|
||||||
|
packageManager.getPackageInfo(callingPackage,
|
||||||
|
PackageManager.GET_SIGNATURES or PackageManager.GET_PERMISSIONS)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the signature of a given package's [PackageInfo].
|
||||||
|
*
|
||||||
|
* The "signature" is a SHA-256 hash of the public key of the signing certificate used by
|
||||||
|
* the app.
|
||||||
|
*
|
||||||
|
* If the app is not found, or if the app does not have exactly one signature, this method
|
||||||
|
* returns `null` as the signature.
|
||||||
|
*/
|
||||||
|
private fun getSignature(packageInfo: PackageInfo): String? {
|
||||||
|
// Security best practices dictate that an app should be signed with exactly one (1)
|
||||||
|
// signature. Because of this, if there are multiple signatures, reject it.
|
||||||
|
if (packageInfo.signatures == null || packageInfo.signatures.size != 1) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
val certificate = packageInfo.signatures[0].toByteArray()
|
||||||
|
return getSignatureSha256(certificate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCertificateWhitelist(parser: XmlResourceParser): Map<String, KnownCallerInfo> {
|
||||||
|
|
||||||
|
val certificateWhitelist = LinkedHashMap<String, KnownCallerInfo>()
|
||||||
|
try {
|
||||||
|
var eventType = parser.next()
|
||||||
|
while (eventType != XmlResourceParser.END_DOCUMENT) {
|
||||||
|
if (eventType == XmlResourceParser.START_TAG) {
|
||||||
|
val callerInfo = when (parser.name) {
|
||||||
|
"signing_certificate" -> parseV1Tag(parser)
|
||||||
|
"signature" -> parseV2Tag(parser)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
callerInfo?.let { info ->
|
||||||
|
val packageName = info.packageName
|
||||||
|
val existingCallerInfo = certificateWhitelist[packageName]
|
||||||
|
if (existingCallerInfo != null) {
|
||||||
|
existingCallerInfo.signatures += callerInfo.signatures
|
||||||
|
} else {
|
||||||
|
certificateWhitelist[packageName] = callerInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventType = parser.next()
|
||||||
|
}
|
||||||
|
} catch (xmlException: XmlPullParserException) {
|
||||||
|
Log.e(TAG, "Could not read allowed callers from XML.", xmlException)
|
||||||
|
} catch (ioException: IOException) {
|
||||||
|
Log.e(TAG, "Could not read allowed callers from XML.", ioException)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificateWhitelist
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a v1 format tag. See allowed_media_browser_callers.xml for more details.
|
||||||
|
*/
|
||||||
|
private fun parseV1Tag(parser: XmlResourceParser): KnownCallerInfo {
|
||||||
|
val name = parser.getAttributeValue(null, "name")
|
||||||
|
val packageName = parser.getAttributeValue(null, "package")
|
||||||
|
val isRelease = parser.getAttributeBooleanValue(null, "release", false)
|
||||||
|
val certificate = parser.nextText().replace(WHITESPACE_REGEX, "")
|
||||||
|
val signature = getSignatureSha256(certificate)
|
||||||
|
|
||||||
|
val callerSignature = KnownSignature(signature, isRelease)
|
||||||
|
return KnownCallerInfo(name, packageName, mutableSetOf(callerSignature))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a v2 format tag. See allowed_media_browser_callers.xml for more details.
|
||||||
|
*/
|
||||||
|
private fun parseV2Tag(parser: XmlResourceParser): KnownCallerInfo {
|
||||||
|
val name = parser.getAttributeValue(null, "name")
|
||||||
|
val packageName = parser.getAttributeValue(null, "package")
|
||||||
|
|
||||||
|
val callerSignatures = mutableSetOf<KnownSignature>()
|
||||||
|
var eventType = parser.next()
|
||||||
|
while (eventType != XmlResourceParser.END_TAG) {
|
||||||
|
val isRelease = parser.getAttributeBooleanValue(null, "release", false)
|
||||||
|
val signature = parser.nextText().replace(WHITESPACE_REGEX, "").toLowerCase()
|
||||||
|
callerSignatures += KnownSignature(signature, isRelease)
|
||||||
|
|
||||||
|
eventType = parser.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return KnownCallerInfo(name, packageName, callerSignatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the Android platform signing key signature. This key is never null.
|
||||||
|
*/
|
||||||
|
private fun getSystemSignature(): String =
|
||||||
|
getPackageInfo(ANDROID_PLATFORM)?.let { platformInfo ->
|
||||||
|
getSignature(platformInfo)
|
||||||
|
} ?: throw IllegalStateException("Platform signature not found")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a SHA-256 signature given a Base64 encoded certificate.
|
||||||
|
*/
|
||||||
|
private fun getSignatureSha256(certificate: String): String {
|
||||||
|
return getSignatureSha256(Base64.decode(certificate, Base64.DEFAULT))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a SHA-256 signature given a certificate byte array.
|
||||||
|
*/
|
||||||
|
private fun getSignatureSha256(certificate: ByteArray): String {
|
||||||
|
val md: MessageDigest
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("SHA256")
|
||||||
|
} catch (noSuchAlgorithmException: NoSuchAlgorithmException) {
|
||||||
|
Log.e(TAG, "No such algorithm: $noSuchAlgorithmException")
|
||||||
|
throw RuntimeException("Could not find SHA256 hash algorithm", noSuchAlgorithmException)
|
||||||
|
}
|
||||||
|
md.update(certificate)
|
||||||
|
|
||||||
|
// This code takes the byte array generated by `md.digest()` and joins each of the bytes
|
||||||
|
// to a string, applying the string format `%02x` on each digit before it's appended, with
|
||||||
|
// a colon (':') between each of the items.
|
||||||
|
// For example: input=[0,2,4,6,8,10,12], output="00:02:04:06:08:0a:0c"
|
||||||
|
return md.digest().joinToString(":") { String.format("%02x", it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class KnownCallerInfo(
|
||||||
|
internal val name: String,
|
||||||
|
internal val packageName: String,
|
||||||
|
internal val signatures: MutableSet<KnownSignature>
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class KnownSignature(
|
||||||
|
internal val signature: String,
|
||||||
|
internal val release: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience class to hold all of the information about an app that's being checked
|
||||||
|
* to see if it's a known caller.
|
||||||
|
*/
|
||||||
|
private data class CallerPackageInfo(
|
||||||
|
internal val name: String,
|
||||||
|
internal val packageName: String,
|
||||||
|
internal val uid: Int,
|
||||||
|
internal val signature: String?,
|
||||||
|
internal val permissions: Set<String>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "PackageValidator"
|
||||||
|
private const val ANDROID_PLATFORM = "android"
|
||||||
|
private val WHITESPACE_REGEX = "\\s|\\n".toRegex()
|
|
@ -38,7 +38,6 @@ import code.name.monkey.retromusic.helper.M3UWriter;
|
||||||
import code.name.monkey.retromusic.model.Playlist;
|
import code.name.monkey.retromusic.model.Playlist;
|
||||||
import code.name.monkey.retromusic.model.PlaylistSong;
|
import code.name.monkey.retromusic.model.PlaylistSong;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import io.reactivex.Observable;
|
|
||||||
|
|
||||||
import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
||||||
|
|
||||||
|
@ -262,7 +261,9 @@ public class PlaylistsUtil {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Observable<File> savePlaylist(Context context, Playlist playlist) throws IOException {
|
@Nullable
|
||||||
|
public static File savePlaylist(@NonNull Context context,
|
||||||
|
@NonNull Playlist playlist) throws IOException {
|
||||||
return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
|
return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,8 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import code.name.monkey.appthemehelper.ThemeStore;
|
import code.name.monkey.appthemehelper.ThemeStore;
|
||||||
import code.name.monkey.appthemehelper.util.ATHUtil;
|
|
||||||
import code.name.monkey.appthemehelper.util.TintHelper;
|
import code.name.monkey.appthemehelper.util.TintHelper;
|
||||||
import code.name.monkey.retromusic.App;
|
import code.name.monkey.retromusic.App;
|
||||||
import code.name.monkey.retromusic.R;
|
|
||||||
|
|
||||||
public class RetroUtil {
|
public class RetroUtil {
|
||||||
|
|
||||||
|
@ -176,6 +174,7 @@ public class RetroUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
|
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
|
||||||
@Nullable Resources.Theme theme) {
|
@Nullable Resources.Theme theme) {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
@ -184,11 +183,11 @@ public class RetroUtil {
|
||||||
return VectorDrawableCompat.create(res, resId, theme);
|
return VectorDrawableCompat.create(res, resId, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id,
|
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id,
|
||||||
@ColorInt int color) {
|
@ColorInt int color) {
|
||||||
return TintHelper
|
return TintHelper.createTintedDrawable(
|
||||||
.createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()),
|
getVectorDrawable(context.getResources(), id, context.getTheme()), color);
|
||||||
color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int id,
|
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int id,
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
tools:ignore="MissingPrefix"
|
tools:ignore="MissingPrefix"
|
||||||
tools:text="Title" />
|
tools:text="Title" />
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextAppearance.MaterialComponents.Subtitle2"
|
style="@style/TextAppearance.MaterialComponents.Subtitle2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
tools:ignore="MissingPrefix"
|
tools:ignore="MissingPrefix"
|
||||||
tools:text="Title" />
|
tools:text="Title" />
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
tools:ignore="MissingPrefix"
|
tools:ignore="MissingPrefix"
|
||||||
tools:text="Title" />
|
tools:text="Title" />
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
tools:ignore="MissingPrefix"
|
tools:ignore="MissingPrefix"
|
||||||
tools:text="Title" />
|
tools:text="Title" />
|
||||||
|
|
||||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
style="@style/TextAppearance.MaterialComponents.Subtitle2"
|
style="@style/TextAppearance.MaterialComponents.Subtitle2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -650,4 +650,14 @@
|
||||||
<string name="saf_guide_slide3_description">Do not open any sub-folders</string>
|
<string name="saf_guide_slide3_description">Do not open any sub-folders</string>
|
||||||
<string name="deleting_songs">Deleting songs</string>
|
<string name="deleting_songs">Deleting songs</string>
|
||||||
<string name="hemanth_savarala">Hemanth Savarala</string>
|
<string name="hemanth_savarala">Hemanth Savarala</string>
|
||||||
|
|
||||||
|
<string name="albums_label">Albums</string>
|
||||||
|
<string name="artists_label">Artists</string>
|
||||||
|
<string name="playlists_label">Playlists</string>
|
||||||
|
<string name="history_label">History</string>
|
||||||
|
<string name="top_tracks_label">Top Tracks</string>
|
||||||
|
<string name="queue_label">Queue</string>
|
||||||
|
<string name="action_cycle_repeat">Cycle repeat mode</string>
|
||||||
|
<string name="action_toggle_shuffle">Toggle shuffle mode</string>
|
||||||
|
<string name="action_toggle_favorite">Toggle favorite</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -150,4 +150,12 @@
|
||||||
<style name="AppTextField" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
|
<style name="AppTextField" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
|
||||||
<item name="boxBackgroundColor">@color/text_field_background</item>
|
<item name="boxBackgroundColor">@color/text_field_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="CarTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<!-- App bg, drawer right side, notification icon badge, overview "now playing" icon -->
|
||||||
|
<item name="colorPrimaryDark">@color/md_deep_purple_500</item>
|
||||||
|
|
||||||
|
<!-- Spinner, progress bar, FAB -->
|
||||||
|
<item name="colorAccent">@color/md_deep_orange_A400</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
309
app/src/main/res/xml/allowed_media_browser_callers.xml
Normal file
309
app/src/main/res/xml/allowed_media_browser_callers.xml
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Copyright (C) 2014 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
If you add a new signing_certificate to allowed_callers and you don't know its signature,
|
||||||
|
PackageValidator by default will print to logcat (INFO level) a message with the proper base64
|
||||||
|
version of the caller's certificate that has not been validated. You can copy from logcat and
|
||||||
|
paste it here.
|
||||||
|
|
||||||
|
Spaces and newlines are ignored.
|
||||||
|
-->
|
||||||
|
<allowed_callers>
|
||||||
|
<signing_certificate
|
||||||
|
name="Android Auto"
|
||||||
|
package="com.google.android.projection.gearhead"
|
||||||
|
release="false">
|
||||||
|
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
|
||||||
|
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
|
||||||
|
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
|
||||||
|
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
|
||||||
|
Fw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzET
|
||||||
|
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
|
||||||
|
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
|
||||||
|
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR
|
||||||
|
24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVy
|
||||||
|
xW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8X
|
||||||
|
W8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC
|
||||||
|
69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexA
|
||||||
|
cKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkw
|
||||||
|
HQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0c
|
||||||
|
xb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||||
|
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
|
||||||
|
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
|
||||||
|
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1Ud
|
||||||
|
EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrP
|
||||||
|
zgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXcla
|
||||||
|
XjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05a
|
||||||
|
IskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+a
|
||||||
|
ayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUW
|
||||||
|
Ev9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Android Auto"
|
||||||
|
package="com.google.android.projection.gearhead"
|
||||||
|
release="false">
|
||||||
|
MIIDvTCCAqWgAwIBAgIJAOfkBvDXw5bzMA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||||
|
aWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDERMA8G
|
||||||
|
A1UEAwwIZ2VhcmhlYWQwHhcNMTQwNTI3MjMwMjUxWhcNNDExMDEyMjMwMjUxWjB1
|
||||||
|
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
|
||||||
|
bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||||
|
aWQxETAPBgNVBAMMCGdlYXJoZWFkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||||
|
CgKCAQEAou7wwBKFyznqpRretJ3EVp55/Yr049Ag5wlGvrCnjIP8DrMrU+skfKe1
|
||||||
|
DmwpsLNtnhhiNH+J000Lok3hc8jdWKeKOopzKGDNvL/HvnS70Zyk26gj9jtMMHz9
|
||||||
|
2aZdpmwD67FNmTlG2FERr+TwMD5agaPnsFR2zla6ugUvHGzz65YDxpCZsQ/TowyD
|
||||||
|
LnxgMagvhvS+Oex3yh2FN7pJfwS03KdGdkWPbLqf9Fem09s5jjeZW/O3RgnKoRPI
|
||||||
|
J4QLK70efjAZqJyBGcDZyQMwOs+8HIknraf8+cRZJDzqOx7rttl8M3KGB2EFljTp
|
||||||
|
6/FyxJLnAo6QlXn7GrYalTI0yLU9dQIDAQABo1AwTjAdBgNVHQ4EFgQU9QPJ5xJE
|
||||||
|
DA8MDQMrj0hm2/A2BRkwHwYDVR0jBBgwFoAU9QPJ5xJEDA8MDQMrj0hm2/A2BRkw
|
||||||
|
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEADcr5h1FR8IpmN4hSsUA9
|
||||||
|
SnCQVyXa1GQhzpQgRbF+npkgOn2Mebp8bd28VpfgooD2OBNQXCUcZkn7pWj++ut9
|
||||||
|
HhObHVaV5FNg0pdDqLna9QZ9Y4oS+ZrijK70XZ/EjlYUHvhu0pIjZAbD8CmCFlow
|
||||||
|
SR55qCSjM5iS37LZB32SMr1BBiYrNAvncKjYQVK8ctTRzhpNQQPBgXBA98Xl+d1D
|
||||||
|
Py00JWQuF0ssmhKcJuvfdEnFF7Hvaxz/gCQ9nzarQI3CJB8dOXVwF8mcyDRBz4JR
|
||||||
|
+YDpXo6BD+fGt15ov+zmqC8xaT9P1/JgoDXiMhy/6rwgdi9WxPf8mb7TnBC+CksX
|
||||||
|
0A==
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Android Auto"
|
||||||
|
package="com.google.android.projection.gearhead"
|
||||||
|
release="true">
|
||||||
|
MIIDvTCCAqWgAwIBAgIJAMePnkuTQTAGMA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||||
|
aWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDERMA8G
|
||||||
|
A1UEAwwIZ2VhcmhlYWQwHhcNMTQwNTI3MjMwNTM0WhcNNDExMDEyMjMwNTM0WjB1
|
||||||
|
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
|
||||||
|
bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||||
|
aWQxETAPBgNVBAMMCGdlYXJoZWFkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||||
|
CgKCAQEA050XDkNIsVRMX2wTvVplpCu4OtnyNK2v5B7PS+DggmH2yuZiwpTurdKD
|
||||||
|
Q9R9UzxH9U4lsC+mIxXkiBYKIWNVgMtiTgxkEy7cgWvdYHgNYpFu8IxZKYDyXes+
|
||||||
|
02pfvpu63MIBD/PnvVFipo1oUrbfetj+mroEpjnA71gUS0Ok+H6XWWsmb8xFHQVM
|
||||||
|
oZWEIzsUJ2nhm8EcnPkAPfNZAG++XLPROoRQCaswyYsd42JuYAP3CwZuhDcUbMWm
|
||||||
|
k7rBi9BVQ8gmkrbwqo94A7qStLUp3NyCmlKSWHaZ05SspEPwsfctka0oXG5bhgT6
|
||||||
|
67EMCzQ+YsFN1oJRL7Qq+mMQjFJs3wIDAQABo1AwTjAdBgNVHQ4EFgQUGvBfYNeu
|
||||||
|
6JSJUnJZCiaBGsnXztswHwYDVR0jBBgwFoAUGvBfYNeu6JSJUnJZCiaBGsnXztsw
|
||||||
|
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAlGsDY0EPu3NBSH5k6iw/
|
||||||
|
wJh9e3xMwS17ErKGlhyWogxJMzLjAN6g0aCPHxB40IQC+8qAl+RL7VQx6oxttf0m
|
||||||
|
31yUGQPcNYbt2CxBTCAr885oLK5t2TAi5tQzhd6ZEYihWSUWUd/X8BQRouxboss9
|
||||||
|
QbBA/iIx0OpDaxiAcq7Cb67TheXZDxGuQ8fmHYbLx84pEvm3DQOB/LIMkkpQSfEC
|
||||||
|
1f+oP1zB3urPU/dSvED/LCgOdrpxZ5di7SwSyue+Vq/TZQy34tPygEzD2d8hFlh/
|
||||||
|
yfhWkMizOeIXcayVAQdNn5zpBkuay1skGOjQQ5kTbDcDzigO2R2rqn6HCd9l5Z0W
|
||||||
|
IQ==
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Android Wear"
|
||||||
|
package="com.google.android.wearable.app"
|
||||||
|
release="false">
|
||||||
|
MIIDvzCCAqegAwIBAgIJANqYw9kVc9PvMA0GCSqGSIb3DQEBBQUAMHYxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||||
|
aWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDESMBAG
|
||||||
|
A1UEAwwJQ2xvY2tXb3JrMB4XDTE0MDMwNzIyMDExOFoXDTQxMDcyMzIyMDExOFow
|
||||||
|
djELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1v
|
||||||
|
dW50YWluIFZpZXcxFDASBgNVBAoMC0dvb2dsZSBJbmMuMRAwDgYDVQQLDAdBbmRy
|
||||||
|
b2lkMRIwEAYDVQQDDAlDbG9ja1dvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||||
|
ggEKAoIBAQDcHW9LKO04MBSynIL22v/THd57jB5jCEBlC1ixZaNqrrYscVOVLgRF
|
||||||
|
Ca+CH5S6n08YZMOntdZTzAAVnQAQ4eVm+jeq/xg2Xa57SoXdsfODzEdwoj6VYpH+
|
||||||
|
tXLBaTFar0706qWuhh/N1ufl6tQxE3RGRgx8KPsyLJKVXFx6qJV3w3A/l+CYt362
|
||||||
|
oG6sa3LqoK0hCrAqH9z8dmJ0dEGpPzzqihb0jJciweMyQTJ+wsn3MDEujRvv7ikL
|
||||||
|
RRo0iSys71sUctbZfvlUKMyK1e8EuMTx9Q3SQtVdclhmhVBbXksbHlmtjB2FL6CC
|
||||||
|
SBVnO8bmQynsxOrU24RkqWsxg5+f28kHAgMBAAGjUDBOMB0GA1UdDgQWBBSEhUcQ
|
||||||
|
hKQ8s+r4P6shYqCVAM4sejAfBgNVHSMEGDAWgBSEhUcQhKQ8s+r4P6shYqCVAM4s
|
||||||
|
ejAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAHOWILon0qD1SIQ60b
|
||||||
|
YI4cKdkBKIHq/D/WKF9fYmqXPvBX5pfusqxcouYFyj0z9ZCZa4sAMsRH5lAPJb0X
|
||||||
|
yvmVAzmDQMjubNy1O+3ksfJI59AgmZ6B58rqpTLP2pn+SqXtQEBORPdb79J/yts1
|
||||||
|
uLIblHhGXhci8nr7KwtuFY5ExKsMT2V7Gdd9j1PJz7nuLU9FtlTgEryN6YHkwuLD
|
||||||
|
055RkwPYrk0swchijXhXrnU/HXsCo6cFeMYF4wUcbB2pSRrOE7uI0H2BfdSUJlGX
|
||||||
|
hK6WlaRHNQ2J60BekPKr82auL8pY0va/Hb9LHEie4KABVN/PAiUS9aHHIp5zHePw
|
||||||
|
R9b4
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Android Wear"
|
||||||
|
package="com.google.android.wearable.app"
|
||||||
|
release="true">
|
||||||
|
MIIFYTCCA0sCBgFEnpGW0zALBgkqhkiG9w0BAQUwdjELMAkGA1UEBhMCVVMxEzAR
|
||||||
|
BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNV
|
||||||
|
BAoTC0dvb2dsZSBJbmMuMRAwDgYDVQQLEwdBbmRyb2lkMRIwEAYDVQQDEwlDbG9j
|
||||||
|
a1dvcmswHhcNMTQwMzA3MjIwMjI1WhcNMzgwMTE5MDMxNDA3WjB2MQswCQYDVQQG
|
||||||
|
EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmll
|
||||||
|
dzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEjAQBgNV
|
||||||
|
BAMTCUNsb2NrV29yazCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALo8
|
||||||
|
fzkL/lmKYrz8izyUxidamRXt3N03OlVqmQvi/UP3nxizAdJAJ+NyfwnO/eKcfCaw
|
||||||
|
iiDeNn0a3+NOp4+uN/OQ9eAmcnpOCCg773b49kO8FSc2oEg/ybeRq1I8872Ge2Yt
|
||||||
|
KidMANiQ550R6LAmX+2pddzI5UKZiY6QE2picYwuCy85eVHbJLFXob/nxWvOSjgL
|
||||||
|
Jfq5JmM+qJQEOOC2lAu5nol+LvoFPDIpm9lhb6S9loIhezdDH83Ygu0hp/LwRn/g
|
||||||
|
lRy8Wphi40oVa+FaF/8CF5hkRNYTsR7XX4OAGO60/ZTkj1rjHOSvpDY4tpcshVzS
|
||||||
|
2woBxDJhKOTFGXq+rMxtwuitpEJfD5DVpaVYJGG/eBHhLs4O6gYDP5ZUOe3gcf/E
|
||||||
|
bCDy374jIzp/ZMHOCa2hy85r9ryiLpuYnErAyWqdbHVP7Bhx2HsQmMGg3mC8fXfe
|
||||||
|
MNVOuEfOaxJ8GR6nk28KRsFG8za5NOq6Wl8cA2S3UpZVRDJQ/WOq5Xvrq+AmPwkI
|
||||||
|
TRlEBgw62bu6f3n03jwrLTe6sw1LuRHcUWngr5VS9NOOPbPyy4AcUgJScGG/AbBC
|
||||||
|
0H6J5I8RqaqgJ/BElZ7aKMXd2FNXpx45u4JRs1frb3IY/MwXGIGmMGdGMeBVlDka
|
||||||
|
emea8lqgYgHWIrjQCd0R1QaiAw8kJ65n2Bs0N3l5AgMBAAEwCwYJKoZIhvcNAQEF
|
||||||
|
A4ICAQCkxJaWNGHIlTWlsQrNASQ3aonaJ0OdrDADSGcLICut4z8vuioHZAO1C+hx
|
||||||
|
yiqym769u8QG2wk5QcmMF2oORv+Q/wAWFgREgG7cguEw/hCGHuMFnbd+PZ2poq00
|
||||||
|
qdK02hsm/VpbcBzVbP7pZHrkFDuXpnwCgGWxf54U8jKl7xfhZKFJF5KWlBwwvVo2
|
||||||
|
q/jzQsjjr7xvSUNzB31qnBHXOSINWte1GS+bHP6Wj0pysbhUdeDpiL5ocohmZbEr
|
||||||
|
9O7DIlZU9eHyK4vrVY6+ZneL1l8JkS35XoCd/u8Px+rKXQ6+HUEEH+cgyzKbMH45
|
||||||
|
LhOX8SA5VGkwhIt/AhdAiS32x6By5984usPXIjVv5lR/anxXit9nyT0rNYiTVDXw
|
||||||
|
+aETzi3szW2hncNLQYLsrtYg61KFMCXF4ATstFG8ReFIWsw2f7ZJkq9ZTFUbC8k4
|
||||||
|
y9Ya1WdZkCD3OmXhqcikiNusgx7rkY8MkikJXt5BBXs8rupOFsW5RUuS4lmKEbSU
|
||||||
|
oh8/er+DwGf0GC6YQZuk5JOKNIDwhi/tr1dySlUzV4/aX7PN/PlUgH//2MlRd+d1
|
||||||
|
BKZCvlzboOEAZfx8aBKc7SezqATXpM3ZDNPsywWoyIpgmtBWoE60ih4Flf05XB+n
|
||||||
|
e7MdpSQ0Xgq9TgG1BoJP6rpC0y3Ukmc+z8AXnYYdJunNXEbv0A==
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Media Browser Service Simulator"
|
||||||
|
package="com.google.android.mediasimulator"
|
||||||
|
release="true">
|
||||||
|
MIIDvTCCAqWgAwIBAgIJAMePnkuTQTAGMA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||||
|
aWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDERMA8G
|
||||||
|
A1UEAwwIZ2VhcmhlYWQwHhcNMTQwNTI3MjMwNTM0WhcNNDExMDEyMjMwNTM0WjB1
|
||||||
|
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
|
||||||
|
bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||||
|
aWQxETAPBgNVBAMMCGdlYXJoZWFkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||||
|
CgKCAQEA050XDkNIsVRMX2wTvVplpCu4OtnyNK2v5B7PS+DggmH2yuZiwpTurdKD
|
||||||
|
Q9R9UzxH9U4lsC+mIxXkiBYKIWNVgMtiTgxkEy7cgWvdYHgNYpFu8IxZKYDyXes+
|
||||||
|
02pfvpu63MIBD/PnvVFipo1oUrbfetj+mroEpjnA71gUS0Ok+H6XWWsmb8xFHQVM
|
||||||
|
oZWEIzsUJ2nhm8EcnPkAPfNZAG++XLPROoRQCaswyYsd42JuYAP3CwZuhDcUbMWm
|
||||||
|
k7rBi9BVQ8gmkrbwqo94A7qStLUp3NyCmlKSWHaZ05SspEPwsfctka0oXG5bhgT6
|
||||||
|
67EMCzQ+YsFN1oJRL7Qq+mMQjFJs3wIDAQABo1AwTjAdBgNVHQ4EFgQUGvBfYNeu
|
||||||
|
6JSJUnJZCiaBGsnXztswHwYDVR0jBBgwFoAUGvBfYNeu6JSJUnJZCiaBGsnXztsw
|
||||||
|
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAlGsDY0EPu3NBSH5k6iw/
|
||||||
|
wJh9e3xMwS17ErKGlhyWogxJMzLjAN6g0aCPHxB40IQC+8qAl+RL7VQx6oxttf0m
|
||||||
|
31yUGQPcNYbt2CxBTCAr885oLK5t2TAi5tQzhd6ZEYihWSUWUd/X8BQRouxboss9
|
||||||
|
QbBA/iIx0OpDaxiAcq7Cb67TheXZDxGuQ8fmHYbLx84pEvm3DQOB/LIMkkpQSfEC
|
||||||
|
1f+oP1zB3urPU/dSvED/LCgOdrpxZ5di7SwSyue+Vq/TZQy34tPygEzD2d8hFlh/
|
||||||
|
yfhWkMizOeIXcayVAQdNn5zpBkuay1skGOjQQ5kTbDcDzigO2R2rqn6HCd9l5Z0W
|
||||||
|
IQ==
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Android Auto Simulator"
|
||||||
|
package="com.google.android.autosimulator"
|
||||||
|
release="true">
|
||||||
|
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
|
||||||
|
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
|
||||||
|
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
|
||||||
|
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
|
||||||
|
Fw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzET
|
||||||
|
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
|
||||||
|
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
|
||||||
|
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR
|
||||||
|
24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVy
|
||||||
|
xW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8X
|
||||||
|
W8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC
|
||||||
|
69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexA
|
||||||
|
cKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkw
|
||||||
|
HQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0c
|
||||||
|
xb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||||
|
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
|
||||||
|
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
|
||||||
|
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1Ud
|
||||||
|
EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrP
|
||||||
|
zgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXcla
|
||||||
|
XjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05a
|
||||||
|
IskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+a
|
||||||
|
ayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUW
|
||||||
|
Ev9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Media Browser Simulator"
|
||||||
|
package="com.google.android.mediasimulator"
|
||||||
|
release="true">
|
||||||
|
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
|
||||||
|
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
|
||||||
|
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
|
||||||
|
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
|
||||||
|
Fw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzET
|
||||||
|
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
|
||||||
|
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
|
||||||
|
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR
|
||||||
|
24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVy
|
||||||
|
xW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8X
|
||||||
|
W8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC
|
||||||
|
69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexA
|
||||||
|
cKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkw
|
||||||
|
HQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0c
|
||||||
|
xb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||||
|
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
|
||||||
|
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
|
||||||
|
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1Ud
|
||||||
|
EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrP
|
||||||
|
zgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXcla
|
||||||
|
XjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05a
|
||||||
|
IskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+a
|
||||||
|
ayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUW
|
||||||
|
Ev9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Google"
|
||||||
|
package="com.google.android.googlequicksearchbox"
|
||||||
|
release="false">
|
||||||
|
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
|
||||||
|
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
|
||||||
|
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
|
||||||
|
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
|
||||||
|
Fw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzET
|
||||||
|
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
|
||||||
|
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
|
||||||
|
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR
|
||||||
|
24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVy
|
||||||
|
xW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8X
|
||||||
|
W8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC
|
||||||
|
69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexA
|
||||||
|
cKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkw
|
||||||
|
HQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0c
|
||||||
|
xb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
|
||||||
|
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
|
||||||
|
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
|
||||||
|
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1Ud
|
||||||
|
EwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrP
|
||||||
|
zgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXcla
|
||||||
|
XjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05a
|
||||||
|
IskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+a
|
||||||
|
ayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUW
|
||||||
|
Ev9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
|
||||||
|
</signing_certificate>
|
||||||
|
<signing_certificate
|
||||||
|
name="Google"
|
||||||
|
package="com.google.android.googlequicksearchbox"
|
||||||
|
release="true">
|
||||||
|
MIIDvTCCAqWgAwIBAgIJAMePnkuTQTAGMA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV
|
||||||
|
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
|
||||||
|
aWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDERMA8G
|
||||||
|
A1UEAwwIZ2VhcmhlYWQwHhcNMTQwNTI3MjMwNTM0WhcNNDExMDEyMjMwNTM0WjB1
|
||||||
|
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
|
||||||
|
bnRhaW4gVmlldzEUMBIGA1UECgwLR29vZ2xlIEluYy4xEDAOBgNVBAsMB0FuZHJv
|
||||||
|
aWQxETAPBgNVBAMMCGdlYXJoZWFkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||||
|
CgKCAQEA050XDkNIsVRMX2wTvVplpCu4OtnyNK2v5B7PS+DggmH2yuZiwpTurdKD
|
||||||
|
Q9R9UzxH9U4lsC+mIxXkiBYKIWNVgMtiTgxkEy7cgWvdYHgNYpFu8IxZKYDyXes+
|
||||||
|
02pfvpu63MIBD/PnvVFipo1oUrbfetj+mroEpjnA71gUS0Ok+H6XWWsmb8xFHQVM
|
||||||
|
oZWEIzsUJ2nhm8EcnPkAPfNZAG++XLPROoRQCaswyYsd42JuYAP3CwZuhDcUbMWm
|
||||||
|
k7rBi9BVQ8gmkrbwqo94A7qStLUp3NyCmlKSWHaZ05SspEPwsfctka0oXG5bhgT6
|
||||||
|
67EMCzQ+YsFN1oJRL7Qq+mMQjFJs3wIDAQABo1AwTjAdBgNVHQ4EFgQUGvBfYNeu
|
||||||
|
6JSJUnJZCiaBGsnXztswHwYDVR0jBBgwFoAUGvBfYNeu6JSJUnJZCiaBGsnXztsw
|
||||||
|
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAlGsDY0EPu3NBSH5k6iw/
|
||||||
|
wJh9e3xMwS17ErKGlhyWogxJMzLjAN6g0aCPHxB40IQC+8qAl+RL7VQx6oxttf0m
|
||||||
|
31yUGQPcNYbt2CxBTCAr885oLK5t2TAi5tQzhd6ZEYihWSUWUd/X8BQRouxboss9
|
||||||
|
QbBA/iIx0OpDaxiAcq7Cb67TheXZDxGuQ8fmHYbLx84pEvm3DQOB/LIMkkpQSfEC
|
||||||
|
1f+oP1zB3urPU/dSvED/LCgOdrpxZ5di7SwSyue+Vq/TZQy34tPygEzD2d8hFlh/
|
||||||
|
yfhWkMizOeIXcayVAQdNn5zpBkuay1skGOjQQ5kTbDcDzigO2R2rqn6HCd9l5Z0W
|
||||||
|
IQ==
|
||||||
|
</signing_certificate>
|
||||||
|
</allowed_callers>
|
Loading…
Reference in a new issue