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:launchMode="singleInstance"
|
||||
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
|
||||
android:name="android.max_aspect"
|
||||
|
@ -240,16 +242,23 @@
|
|||
|
||||
<service
|
||||
android:name=".service.MusicService"
|
||||
android:enabled="true" />
|
||||
<service
|
||||
android:name=".service.WearBrowserService"
|
||||
android:exported="true">
|
||||
android:enabled="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</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>
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
|
|
@ -19,49 +19,6 @@ import android.provider.MediaStore
|
|||
|
||||
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 TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534"
|
||||
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 + " != ''"
|
||||
|
||||
@JvmField
|
||||
val BASE_PROJECTION = arrayOf(BaseColumns._ID, // 0
|
||||
MediaStore.Audio.AudioColumns.TITLE, // 1
|
||||
MediaStore.Audio.AudioColumns.TRACK, // 2
|
||||
|
|
|
@ -77,6 +77,9 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
|||
|
||||
ActivityCompat.postponeEnterTransition(this)
|
||||
|
||||
|
||||
artistImage = findViewById(R.id.artistImage)
|
||||
|
||||
val albumId = intent.getIntExtra(EXTRA_ALBUM_ID, -1)
|
||||
albumDetailsPresenter = AlbumDetailsPresenter(this, albumId)
|
||||
albumDetailsPresenter.subscribe()
|
||||
|
@ -84,7 +87,6 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
|||
setupRecyclerView()
|
||||
setupToolbarMarginHeight()
|
||||
|
||||
artistImage = findViewById(R.id.artistImage)
|
||||
artistImage.setOnClickListener {
|
||||
val artistPairs = arrayOf<Pair<*, *>>(Pair.create(image, resources.getString(R.string.transition_artist_image)))
|
||||
NavigationUtil.goToArtist(this, album.artistId, *artistPairs)
|
||||
|
@ -189,9 +191,7 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac
|
|||
private lateinit var artistImage: ImageView
|
||||
|
||||
private fun loadMoreFrom(album: Album) {
|
||||
disposable.add(ArtistLoader.getArtist(this, album.artistId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
disposable.add(ArtistLoader.getArtistFlowable(this, album.artistId)
|
||||
.map {
|
||||
GlideApp.with(this@AlbumDetailsActivity)
|
||||
.asBitmapPalette()
|
||||
|
|
|
@ -6,14 +6,12 @@ import android.content.pm.PackageManager
|
|||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import code.name.monkey.retromusic.R
|
||||
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.folders.FoldersFragment
|
||||
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment
|
||||
|
@ -121,7 +119,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP
|
|||
PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this)
|
||||
}
|
||||
|
||||
private fun setCurrentFragment(fragment: Fragment, b: Boolean) {
|
||||
private fun setCurrentFragment(fragment: Fragment, b: Boolean = false) {
|
||||
val trans = supportFragmentManager.beginTransaction()
|
||||
trans.replace(R.id.fragment_container, fragment, null)
|
||||
if (b) {
|
||||
|
@ -162,7 +160,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP
|
|||
val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt()
|
||||
if (id >= 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)
|
||||
handled = true
|
||||
}
|
||||
|
@ -170,14 +168,14 @@ class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedP
|
|||
val id = parseIdFromIntent(intent, "albumId", "album").toInt()
|
||||
if (id >= 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
|
||||
}
|
||||
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
|
||||
val id = parseIdFromIntent(intent, "artistId", "artist").toInt()
|
||||
if (id >= 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
|
|||
// Playlist renamed
|
||||
val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist!!.id.toLong())
|
||||
if (playlistName != playlist!!.name) {
|
||||
playlist = PlaylistLoader.getPlaylist(this, playlist!!.id).blockingFirst()
|
||||
playlist = PlaylistLoader.getPlaylist(this, playlist!!.id)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,15 +4,10 @@ import android.Manifest
|
|||
import android.content.*
|
||||
import android.os.Bundle
|
||||
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.helper.MusicPlayerRemote
|
||||
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
|||
}
|
||||
|
||||
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)
|
||||
for (song in songs) {
|
||||
paths.add(song.data)
|
||||
|
|
|
@ -108,7 +108,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
|
|||
|
||||
override fun getSongPaths(): List<String> {
|
||||
val paths = ArrayList<String>(1)
|
||||
paths.add(SongLoader.getSong(this, id).blockingFirst().data)
|
||||
paths.add(SongLoader.getSong(this, id).data)
|
||||
return paths
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ class HomeAdapter(private val activity: AppCompatActivity, private var homes: Li
|
|||
}
|
||||
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||
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 {
|
||||
val songAdapter = SongAdapter(activity, songs, R.layout.item_album_card, false, null)
|
||||
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>() {
|
||||
|
||||
fun swapDataSet(dataSet: ArrayList<Any>) {
|
||||
fun swapDataSet(dataSet: MutableList<Any>) {
|
||||
this.dataSet = dataSet
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
|
|
@ -149,10 +149,9 @@ class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayL
|
|||
val songs = ArrayList<Song>()
|
||||
for (playlist in playlists) {
|
||||
if (playlist is AbsCustomPlaylist) {
|
||||
songs.addAll(playlist.getSongs(activity).blockingFirst())
|
||||
songs.addAll(playlist.getSongs(activity))
|
||||
} else {
|
||||
songs
|
||||
.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id).blockingFirst())
|
||||
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
|
||||
}
|
||||
}
|
||||
return songs
|
||||
|
@ -161,9 +160,9 @@ class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayL
|
|||
private fun getSongs(playlist: Playlist): ArrayList<Song> {
|
||||
val songs = ArrayList<Song>()
|
||||
if (playlist is AbsSmartPlaylist) {
|
||||
songs.addAll(playlist.getSongs(activity).blockingFirst())
|
||||
songs.addAll(playlist.getSongs(activity))
|
||||
} else {
|
||||
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id).blockingFirst())
|
||||
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
|
||||
}
|
||||
return songs
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ package code.name.monkey.retromusic.appshortcuts
|
|||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import code.name.monkey.retromusic.Constants.ACTION_PLAY_PLAYLIST
|
||||
import code.name.monkey.retromusic.Constants.INTENT_EXTRA_PLAYLIST
|
||||
import code.name.monkey.retromusic.Constants.INTENT_EXTRA_SHUFFLE_MODE
|
||||
import code.name.monkey.retromusic.activities.SearchActivity
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
|
||||
import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType
|
||||
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.ShuffleAllPlaylist
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.activities.SearchActivity
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
|
||||
|
||||
class AppShortcutLauncherActivity : Activity() {
|
||||
|
|
|
@ -24,14 +24,14 @@ import android.text.TextUtils
|
|||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.Constants
|
||||
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.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||
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 com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
@ -149,15 +149,15 @@ class AppWidgetBig : BaseAppWidget() {
|
|||
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// 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)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||
|
||||
|
||||
|
|
|
@ -24,15 +24,15 @@ import android.text.TextUtils
|
|||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.Constants
|
||||
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.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
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 com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
@ -50,9 +50,9 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
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_prev, BaseAppWidget.Companion.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_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_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, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
|
@ -128,10 +128,10 @@ class AppWidgetCard : BaseAppWidget() {
|
|||
val playPauseRest = if (isPlaying) R.drawable.ic_pause_white_24dp
|
||||
else
|
||||
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
|
||||
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_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_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, createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color)!!, 1f))
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
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)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// 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)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
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.service.MusicService
|
||||
import code.name.monkey.retromusic.activities.MainActivity
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
@ -49,9 +50,9 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
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_prev, BaseAppWidget.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_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_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, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
|
@ -116,11 +117,11 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
else
|
||||
R.drawable.ic_play_arrow_white_32dp
|
||||
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
|
||||
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_prev, BaseAppWidget.createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_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, createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, color)!!, 1f))
|
||||
|
||||
val image = getAlbumArtDrawable(service.resources, bitmap)
|
||||
val roundedBitmap = BaseAppWidget.createRoundedBitmap(image, imageSize, imageSize,
|
||||
|
@ -149,15 +150,15 @@ class AppWidgetClassic : BaseAppWidget() {
|
|||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// 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)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||
}
|
||||
|
||||
|
|
|
@ -24,15 +24,15 @@ import android.text.TextUtils
|
|||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import code.name.monkey.retromusic.Constants
|
||||
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.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
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 com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
|
@ -49,9 +49,9 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
|
||||
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
|
||||
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_prev, BaseAppWidget.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_next, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_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, createBitmap(RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_32dp, MaterialValueHelper.getSecondaryTextColor(context, true))!!, 1f))
|
||||
|
||||
linkButtons(context, appWidgetView)
|
||||
pushUpdate(context, appWidgetIds, appWidgetView)
|
||||
|
@ -153,15 +153,15 @@ class AppWidgetSmall : BaseAppWidget() {
|
|||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// 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)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,20 +23,20 @@ import android.view.View
|
|||
import android.widget.RemoteViews
|
||||
import androidx.core.content.ContextCompat
|
||||
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.activities.MainActivity
|
||||
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
|
||||
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
|
||||
|
||||
class AppWidgetText : BaseAppWidget() {
|
||||
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
|
||||
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_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_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_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, 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, 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.text, ContextCompat.getColor(context, R.color.md_white_1000))
|
||||
|
@ -61,15 +61,15 @@ class AppWidgetText : BaseAppWidget() {
|
|||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||
|
||||
// Previous track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
|
||||
|
||||
// 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)
|
||||
|
||||
// Next track
|
||||
pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName)
|
||||
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
|
||||
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
|
||||
}
|
||||
|
||||
|
@ -95,9 +95,9 @@ class AppWidgetText : BaseAppWidget() {
|
|||
R.drawable.ic_pause_white_24dp
|
||||
else
|
||||
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_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_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_toggle_play_pause, createBitmap(RetroUtil.getTintedVectorDrawable(context, playPauseRes, 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, 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 androidx.core.content.ContextCompat
|
||||
import code.name.monkey.retromusic.App
|
||||
import code.name.monkey.retromusic.Constants
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
|
||||
abstract class BaseAppWidget : AppWidgetProvider() {
|
||||
|
||||
|
@ -42,8 +42,8 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray) {
|
||||
defaultAppWidget(context, appWidgetIds)
|
||||
val updateIntent = Intent(Constants.APP_WIDGET_UPDATE)
|
||||
updateIntent.putExtra(Constants.EXTRA_APP_WIDGET_NAME, NAME)
|
||||
val updateIntent = Intent(APP_WIDGET_UPDATE)
|
||||
updateIntent.putExtra(EXTRA_APP_WIDGET_NAME, NAME)
|
||||
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
||||
updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
|
||||
context.sendBroadcast(updateIntent)
|
||||
|
@ -54,7 +54,7 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
|||
*/
|
||||
fun notifyChange(service: MusicService, what: String) {
|
||||
if (hasInstances(service)) {
|
||||
if (Constants.META_CHANGED == what || Constants.PLAY_STATE_CHANGED == what) {
|
||||
if (META_CHANGED == what || PLAY_STATE_CHANGED == what) {
|
||||
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.bottomsheets.BottomSheet
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import kotlinx.android.synthetic.main.activity_user_info.*
|
||||
|
||||
|
||||
class AddToPlaylistDialog : DialogFragment() {
|
||||
|
@ -33,7 +32,7 @@ class AddToPlaylistDialog : DialogFragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): Dialog {
|
||||
val cntx = requireContext()
|
||||
val playlists = PlaylistLoader.getAllPlaylists(cntx).blockingFirst()
|
||||
val playlists = PlaylistLoader.getAllPlaylists(cntx)
|
||||
val playlistNames: MutableList<String> = mutableListOf()
|
||||
playlistNames.add(cntx.resources.getString(R.string.action_new_playlist))
|
||||
for (p in playlists) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import android.app.AlarmManager
|
|||
import android.app.Dialog
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
|
@ -29,10 +28,11 @@ import android.widget.TextView
|
|||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.retromusic.Constants
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
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.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.ViewUtil
|
||||
|
@ -153,8 +153,8 @@ class SleepTimerDialog : DialogFragment() {
|
|||
private fun makeTimerIntent(): Intent {
|
||||
val intent = Intent(activity, MusicService::class.java)
|
||||
return if (shouldFinishLastSong.isChecked) {
|
||||
intent.setAction(Constants.ACTION_PENDING_QUIT)
|
||||
} else intent.setAction(Constants.ACTION_QUIT)
|
||||
intent.setAction(ACTION_PENDING_QUIT)
|
||||
} else intent.setAction(ACTION_QUIT)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -572,7 +572,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
|
|||
return null;
|
||||
}
|
||||
|
||||
return FileUtil.matchFilesWithMediaStore(context, files).blockingFirst();
|
||||
return FileUtil.matchFilesWithMediaStore(context, files);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
cancel(false);
|
||||
|
|
|
@ -105,7 +105,7 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
|
|||
}
|
||||
|
||||
actionShuffle.setOnClickListener {
|
||||
MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(mainActivity).blockingFirst(), true)
|
||||
MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(mainActivity) , true)
|
||||
}
|
||||
|
||||
history.setOnClickListener {
|
||||
|
|
|
@ -213,7 +213,7 @@ class FullPlayerFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbac
|
|||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
private fun updateArtistImage() {
|
||||
compositeDisposable.addAll(ArtistLoader.getArtist(context!!, MusicPlayerRemote.currentSong.artistId)
|
||||
compositeDisposable.addAll(ArtistLoader.getArtistFlowable(context!!, MusicPlayerRemote.currentSong.artistId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
|
|
|
@ -12,13 +12,11 @@
|
|||
* See the GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.helper
|
||||
package code.name.monkey.retromusic.helper;
|
||||
|
||||
interface M3UConstants {
|
||||
companion object {
|
||||
val EXTENSION = "m3u"
|
||||
val HEADER = "#EXTM3U"
|
||||
val ENTRY = "#EXTINF:"
|
||||
val DURATION_SEPARATOR = ","
|
||||
}
|
||||
public interface M3UConstants {
|
||||
String EXTENSION = "m3u";
|
||||
String HEADER = "#EXTM3U";
|
||||
String ENTRY = "#EXTINF:";
|
||||
String DURATION_SEPARATOR = ",";
|
||||
}
|
|
@ -16,47 +16,43 @@ package code.name.monkey.retromusic.helper;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
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.Song;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
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
|
||||
dir.mkdirs();
|
||||
File file = new File(dir, playlist.name.concat("." + M3UConstants.Companion.getEXTENSION()));
|
||||
|
||||
ArrayList<? extends Song> songs;
|
||||
if (playlist instanceof AbsCustomPlaylist) {
|
||||
songs = ((AbsCustomPlaylist) playlist).getSongs(context).blockingFirst();
|
||||
} else {
|
||||
songs = PlaylistSongsLoader.INSTANCE.getPlaylistSongList(context, playlist.id).blockingFirst();
|
||||
}
|
||||
File file = new File(dir, playlist.name.concat("." + EXTENSION));
|
||||
|
||||
ArrayList<Song> songs = playlist.getSongs(context);
|
||||
|
||||
if (songs.size() > 0) {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
|
||||
|
||||
bw.write(M3UConstants.Companion.getHEADER());
|
||||
bw.write(HEADER);
|
||||
for (Song song : songs) {
|
||||
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.write(song.getData());
|
||||
}
|
||||
|
||||
bw.close();
|
||||
}
|
||||
|
||||
return Observable.just(file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import code.name.monkey.retromusic.loaders.SongLoader
|
|||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
@ -366,6 +365,7 @@ object MusicPlayerRemote {
|
|||
|
||||
fun playFromUri(uri: Uri) {
|
||||
if (musicService != null) {
|
||||
|
||||
var songs: ArrayList<Song>? = null
|
||||
if (uri.scheme != null && uri.authority != null) {
|
||||
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
|
||||
|
@ -376,23 +376,18 @@ object MusicPlayerRemote {
|
|||
songId = uri.lastPathSegment
|
||||
}
|
||||
if (songId != null) {
|
||||
/* songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
musicService,
|
||||
MediaStore.Audio.AudioColumns._ID + "=?",
|
||||
new String[]{songId}
|
||||
));*/
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
musicService!!,
|
||||
MediaStore.Audio.AudioColumns._ID + "=?",
|
||||
arrayOf(songId)))
|
||||
.subscribeOn(Schedulers.io()).blockingFirst()
|
||||
arrayOf(songId)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (songs == null) {
|
||||
var songFile: File? = null
|
||||
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) {
|
||||
val path = getFilePathFromUri(musicService!!, uri)
|
||||
|
@ -403,8 +398,11 @@ object MusicPlayerRemote {
|
|||
songFile = File(uri.path)
|
||||
}
|
||||
if (songFile != null) {
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(musicService!!, MediaStore.Audio.AudioColumns.DATA + "=?", arrayOf(songFile.absolutePath)
|
||||
)).blockingFirst()
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
musicService!!,
|
||||
MediaStore.Audio.AudioColumns.DATA + "=?",
|
||||
arrayOf(songFile.absolutePath)
|
||||
))
|
||||
}
|
||||
}
|
||||
if (songs != null && songs.isNotEmpty()) {
|
||||
|
|
|
@ -38,52 +38,52 @@ object SearchQueryHelper {
|
|||
|
||||
var songs = ArrayList<Song>()
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, arrayOf(query.toLowerCase()))).blockingFirst()
|
||||
if (!songs.isEmpty()) {
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, arrayOf(query.toLowerCase())))
|
||||
if (songs.isNotEmpty()) {
|
||||
return songs
|
||||
}
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, arrayOf(query.toLowerCase()))).blockingFirst()
|
||||
return if (!songs.isEmpty()) {
|
||||
songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, arrayOf(query.toLowerCase())))
|
||||
return if (songs.isNotEmpty()) {
|
||||
songs
|
||||
} else ArrayList()
|
||||
}
|
||||
|
|
|
@ -51,6 +51,6 @@ object GenreMenuHelper {
|
|||
}
|
||||
|
||||
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,
|
||||
playlist: Playlist): ArrayList<Song> {
|
||||
val songs: ArrayList<Song>
|
||||
if (playlist is AbsCustomPlaylist) {
|
||||
songs = playlist.getSongs(activity).blockingFirst()
|
||||
return if (playlist is AbsCustomPlaylist) {
|
||||
playlist.getSongs(activity)
|
||||
} 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) {
|
||||
|
||||
override fun doInBackground(vararg params: Playlist): 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) {
|
||||
|
|
|
@ -21,24 +21,39 @@ import code.name.monkey.retromusic.model.Song
|
|||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import io.reactivex.Observable
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 11/08/17.
|
||||
*/
|
||||
|
||||
open class AlbumLoader {
|
||||
companion object {
|
||||
fun getAllAlbums(context: Context): Observable<ArrayList<Album>> {
|
||||
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
object AlbumLoader {
|
||||
fun getAllAlbumsFlowable(
|
||||
context: Context
|
||||
): Observable<ArrayList<Album>> {
|
||||
val songs = SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||
context, null, null,
|
||||
getSongLoaderSortOrder())
|
||||
)
|
||||
|
||||
return splitIntoAlbums(songs)
|
||||
return splitIntoAlbumsFlowable(songs)
|
||||
}
|
||||
|
||||
fun getAlbums(context: Context,
|
||||
query: String): Observable<ArrayList<Album>> {
|
||||
fun getAlbumsFlowable(context: Context, query: String): Observable<ArrayList<Album>> {
|
||||
val songs = SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||
context,
|
||||
AudioColumns.ALBUM + " LIKE ?",
|
||||
arrayOf("%$query%"),
|
||||
getSongLoaderSortOrder())
|
||||
)
|
||||
return splitIntoAlbumsFlowable(songs)
|
||||
}
|
||||
|
||||
fun getAlbums(
|
||||
context: Context,
|
||||
query: String
|
||||
): ArrayList<Album> {
|
||||
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
context,
|
||||
AudioColumns.ALBUM + " LIKE ?",
|
||||
|
@ -48,11 +63,12 @@ open class AlbumLoader {
|
|||
return splitIntoAlbums(songs)
|
||||
}
|
||||
|
||||
fun getAlbum(context: Context, albumId: Int): Observable<Album> {
|
||||
fun getAlbumFlowable(
|
||||
context: Context,
|
||||
albumId: Int
|
||||
): Observable<Album> {
|
||||
return Observable.create { e ->
|
||||
val songs = SongLoader.getSongs(SongLoader
|
||||
.makeSongCursor(context, AudioColumns.ALBUM_ID + "=?",
|
||||
arrayOf(albumId.toString()), getSongLoaderSortOrder()))
|
||||
val songs = SongLoader.getSongsFlowable(SongLoader.makeSongCursor(context, AudioColumns.ALBUM_ID + "=?", arrayOf(albumId.toString()), getSongLoaderSortOrder()))
|
||||
songs.subscribe { songs1 ->
|
||||
e.onNext(Album(songs1))
|
||||
e.onComplete()
|
||||
|
@ -60,31 +76,71 @@ open class AlbumLoader {
|
|||
}
|
||||
}
|
||||
|
||||
fun splitIntoAlbums(
|
||||
songs: Observable<ArrayList<Song>>?): Observable<ArrayList<Album>> {
|
||||
fun getAlbum(
|
||||
context: Context,
|
||||
albumId: Int
|
||||
): Album {
|
||||
val songs = SongLoader.getSongs(
|
||||
SongLoader.makeSongCursor(
|
||||
context,
|
||||
AudioColumns.ALBUM_ID + "=?",
|
||||
arrayOf(albumId.toString()),
|
||||
getSongLoaderSortOrder()))
|
||||
val album = Album(songs)
|
||||
sortSongsByTrackNumber(album)
|
||||
return album
|
||||
}
|
||||
|
||||
fun splitIntoAlbumsFlowable(
|
||||
songs: Observable<ArrayList<Song>>?
|
||||
): Observable<ArrayList<Album>> {
|
||||
return Observable.create { e ->
|
||||
val albums = ArrayList<Album>()
|
||||
songs?.subscribe { songs1 ->
|
||||
for (song in songs1) {
|
||||
getOrCreateAlbum(albums, song.albumId).subscribe { album -> album.songs!!.add(song) }
|
||||
getOrCreateAlbumFlowable(albums, song.albumId).subscribe { album ->
|
||||
album.songs!!.add(song)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (album in albums) {
|
||||
sortSongsByTrackNumber(album)
|
||||
}
|
||||
e.onNext(albums)
|
||||
e.onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
fun splitIntoAlbums(songs: ArrayList<Song>?): ArrayList<Album> {
|
||||
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).subscribe { album -> album.songs!!.add(song) }
|
||||
getOrCreateAlbum(albums, song.albumId).songs?.add(song)
|
||||
}
|
||||
}
|
||||
for (album in albums) {
|
||||
sortSongsByTrackNumber(album)
|
||||
}
|
||||
return albums
|
||||
}
|
||||
|
||||
private fun getOrCreateAlbum(albums: ArrayList<Album>, albumId: Int): Observable<Album> {
|
||||
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) {
|
||||
|
@ -100,10 +156,29 @@ open class AlbumLoader {
|
|||
}
|
||||
}
|
||||
|
||||
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.util.PreferenceUtil
|
||||
import io.reactivex.Observable
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
object ArtistLoader {
|
||||
private fun getSongLoaderSortOrder(): String {
|
||||
|
@ -30,23 +30,11 @@ object ArtistLoader {
|
|||
PreferenceUtil.getInstance().artistDetailSongSortOrder
|
||||
}
|
||||
|
||||
fun getArtist(context: Context, artistId: Int): Observable<Artist> {
|
||||
fun getAllArtistsFlowable(
|
||||
context: Context
|
||||
): Observable<ArrayList<Artist>> {
|
||||
return Observable.create { e ->
|
||||
SongLoader.getSongs(SongLoader.makeSongCursor(context, AudioColumns.ARTIST_ID + "=?",
|
||||
arrayOf(artistId.toString()),
|
||||
getSongLoaderSortOrder()))
|
||||
.subscribe { songs ->
|
||||
val artist = Artist(AlbumLoader.splitIntoAlbums(songs))
|
||||
e.onNext(artist)
|
||||
e.onComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllArtists(context: Context): Observable<ArrayList<Artist>> {
|
||||
return Observable.create { e ->
|
||||
SongLoader
|
||||
.getSongs(SongLoader.makeSongCursor(
|
||||
SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||
context, null, null,
|
||||
getSongLoaderSortOrder())
|
||||
).subscribe { songs ->
|
||||
|
@ -54,12 +42,20 @@ object ArtistLoader {
|
|||
e.onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getArtists(context: Context, query: String): Observable<ArrayList<Artist>> {
|
||||
fun getAllArtists(context: Context): ArrayList<Artist> {
|
||||
val songs = SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
context,
|
||||
null, null,
|
||||
getSongLoaderSortOrder())
|
||||
)
|
||||
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))
|
||||
}
|
||||
|
||||
fun getArtistsFlowable(context: Context, query: String): Observable<ArrayList<Artist>> {
|
||||
return Observable.create { e ->
|
||||
SongLoader.getSongs(SongLoader.makeSongCursor(
|
||||
SongLoader.getSongsFlowable(SongLoader.makeSongCursor(
|
||||
context,
|
||||
AudioColumns.ARTIST + " LIKE ?",
|
||||
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> {
|
||||
val artists = ArrayList<Artist>()
|
||||
if (albums != null) {
|
||||
|
@ -83,7 +89,7 @@ object ArtistLoader {
|
|||
|
||||
private fun getOrCreateArtist(artists: ArrayList<Artist>, artistId: Int): Artist {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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 java.util.*
|
||||
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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,
|
||||
// so we need to get songs without a genre a different way.
|
||||
return if (genreId == -1) {
|
||||
|
@ -45,12 +59,18 @@ object GenreLoader {
|
|||
private fun getGenreFromCursor(context: Context, cursor: Cursor): Genre {
|
||||
val id = cursor.getInt(0)
|
||||
val name = cursor.getString(1)
|
||||
val songCount = getSongs(context, id).blockingFirst().size
|
||||
val songCount = getSongs(context, id).size
|
||||
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 " +
|
||||
"(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
|
||||
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 ->
|
||||
val genres = ArrayList<Genre>()
|
||||
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? {
|
||||
val projection = arrayOf(Genres._ID, Genres.NAME)
|
||||
|
|
|
@ -25,47 +25,5 @@ import io.reactivex.Observable
|
|||
|
||||
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.util.PreferenceUtil
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.annotations.NonNull
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Created by hemanths on 16/08/17.
|
||||
|
@ -31,12 +31,16 @@ import java.util.*
|
|||
|
||||
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))
|
||||
}
|
||||
|
||||
private fun makeLastAddedCursor(@NonNull context: Context): Cursor? {
|
||||
private fun makeLastAddedCursor(context: Context): Cursor? {
|
||||
val cutoff = PreferenceUtil.getInstance().lastAddedCutoff
|
||||
|
||||
return SongLoader.makeSongCursor(
|
||||
|
@ -46,13 +50,22 @@ object LastAddedSongsLoader {
|
|||
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))
|
||||
}
|
||||
|
||||
@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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,31 +19,20 @@ import android.database.Cursor
|
|||
import android.provider.BaseColumns
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.PlaylistsColumns
|
||||
import code.name.monkey.retromusic.R
|
||||
import code.name.monkey.retromusic.model.Playlist
|
||||
import io.reactivex.Observable
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 16/08/17.
|
||||
*/
|
||||
|
||||
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) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlaylist(cursor: Cursor?): Observable<Playlist> {
|
||||
private fun getPlaylistFlowable(
|
||||
cursor: Cursor?
|
||||
): Observable<Playlist> {
|
||||
return Observable.create { e ->
|
||||
var playlist = Playlist()
|
||||
|
||||
|
@ -55,11 +44,35 @@ object PlaylistLoader {
|
|||
e.onNext(playlist)
|
||||
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(
|
||||
context,
|
||||
PlaylistsColumns.NAME + "=?",
|
||||
|
@ -67,23 +80,31 @@ object PlaylistLoader {
|
|||
))
|
||||
}
|
||||
|
||||
fun getPlaylist(context: Context, playlistId: Int): Observable<Playlist> {
|
||||
return getPlaylist(makePlaylistCursor(
|
||||
fun getPlaylistFlowable(
|
||||
context: Context,
|
||||
playlistId: Int
|
||||
): Observable<Playlist> {
|
||||
return getPlaylistFlowable(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)
|
||||
fun getAllPlaylistsFlowoable(
|
||||
context: Context
|
||||
): Observable<ArrayList<Playlist>> {
|
||||
return getAllPlaylistsFlowable(makePlaylistCursor(context, null, null))
|
||||
}
|
||||
|
||||
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 ->
|
||||
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))
|
||||
}
|
||||
|
||||
fun getFavoritePlaylist(context: Context): Observable<ArrayList<Playlist>> {
|
||||
fun getFavoritePlaylist(context: Context): ArrayList<Playlist> {
|
||||
return getAllPlaylists(makePlaylistCursor(
|
||||
context,
|
||||
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) {
|
||||
|
@ -118,4 +151,42 @@ object PlaylistLoader {
|
|||
localStringBuilder.append(")")
|
||||
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.Song
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.annotations.NonNull
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 16/08/17.
|
||||
*/
|
||||
|
||||
object PlaylistSongsLoader {
|
||||
|
||||
@NonNull
|
||||
fun getPlaylistSongList(@NonNull context: Context, playlist: Playlist): Observable<ArrayList<Song>> {
|
||||
fun getPlaylistSongListFlowable(
|
||||
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)
|
||||
?: 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 ->
|
||||
val songs = ArrayList<Song>()
|
||||
val cursor = makePlaylistSongCursor(context, playlistId)
|
||||
|
@ -56,8 +66,21 @@ object PlaylistSongsLoader {
|
|||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private fun getPlaylistSongFromCursorImpl(@NonNull cursor: Cursor, playlistId: Int): PlaylistSong {
|
||||
fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList<Song> {
|
||||
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 title = cursor.getString(1)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun makePlaylistSongCursor(@NonNull context: Context, playlistId: Int): Cursor? {
|
||||
private fun makePlaylistSongCursor(context: Context, playlistId: Int): Cursor? {
|
||||
try {
|
||||
return context.contentResolver.query(
|
||||
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()),
|
||||
|
|
|
@ -15,42 +15,33 @@
|
|||
package code.name.monkey.retromusic.loaders
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import code.name.monkey.retromusic.R
|
||||
import io.reactivex.Observable
|
||||
import java.util.*
|
||||
|
||||
|
||||
object SearchLoader {
|
||||
|
||||
fun searchAll(context: Context, query: String?): Observable<ArrayList<Any>> {
|
||||
val results = ArrayList<Any>()
|
||||
return Observable.create { e ->
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
SongLoader.getSongs(context, query!!)
|
||||
.subscribe { songs ->
|
||||
if (!songs.isEmpty()) {
|
||||
fun searchAll(context: Context, query: String?): MutableList<Any> {
|
||||
val results = mutableListOf<Any>()
|
||||
query?.let {
|
||||
val songs = SongLoader.getSongs(context, it)
|
||||
if (songs.isNotEmpty()) {
|
||||
results.add(context.resources.getString(R.string.songs))
|
||||
results.addAll(songs)
|
||||
}
|
||||
}
|
||||
|
||||
ArtistLoader.getArtists(context, query)
|
||||
.subscribe { artists ->
|
||||
if (!artists.isEmpty()) {
|
||||
val artists = ArtistLoader.getArtists(context, it)
|
||||
if (artists.isNotEmpty()) {
|
||||
results.add(context.resources.getString(R.string.artists))
|
||||
results.addAll(artists)
|
||||
}
|
||||
}
|
||||
AlbumLoader.getAlbums(context, query)
|
||||
.subscribe { albums ->
|
||||
if (!albums.isEmpty()) {
|
||||
|
||||
val albums = AlbumLoader.getAlbums(context, it)
|
||||
if (albums.isNotEmpty()) {
|
||||
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 java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 10/08/17.
|
||||
*/
|
||||
|
||||
|
||||
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)
|
||||
return getSongs(cursor)
|
||||
}
|
||||
|
||||
fun getSongs(context: Context, query: String): Observable<ArrayList<Song>> {
|
||||
val cursor = makeSongCursor(context, AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%"))
|
||||
return getSongs(cursor)
|
||||
}
|
||||
|
||||
fun getSongs(cursor: Cursor?): Observable<ArrayList<Song>> {
|
||||
fun getSongsFlowable(
|
||||
cursor: Cursor?
|
||||
): Observable<ArrayList<Song>> {
|
||||
return Observable.create { e ->
|
||||
val songs = ArrayList<Song>()
|
||||
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 title = cursor.getString(1)
|
||||
val trackNumber = cursor.getInt(2)
|
||||
|
@ -78,7 +172,12 @@ object SongLoader {
|
|||
}
|
||||
|
||||
@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 selectionValuesFinal = selectionValues
|
||||
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(
|
||||
if (selection != null && selection.trim { it <= ' ' } != "") "$selection AND " else "")
|
||||
newSelection.append(AudioColumns.DATA + " NOT LIKE ?")
|
||||
|
@ -113,8 +215,10 @@ object SongLoader {
|
|||
return newSelection.toString()
|
||||
}
|
||||
|
||||
private fun addBlacklistSelectionValues(selectionValues: Array<String>?,
|
||||
paths: ArrayList<String>): Array<String>? {
|
||||
private fun addBlacklistSelectionValues(
|
||||
selectionValues: Array<String>?,
|
||||
paths: ArrayList<String>
|
||||
): Array<String>? {
|
||||
var selectionValuesFinal = selectionValues
|
||||
if (selectionValuesFinal == null) {
|
||||
selectionValuesFinal = emptyArray()
|
||||
|
@ -128,35 +232,4 @@ object SongLoader {
|
|||
}
|
||||
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 {
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -130,9 +138,11 @@ object TopAndRecentlyPlayedTracksLoader {
|
|||
return null
|
||||
}
|
||||
|
||||
fun getTopAlbums(context: Context): Observable<ArrayList<Album>> {
|
||||
fun getTopAlbumsFlowable(
|
||||
context: Context
|
||||
): Observable<ArrayList<Album>> {
|
||||
return Observable.create { e ->
|
||||
getTopTracks(context).subscribe { songs ->
|
||||
getTopTracksFlowable(context).subscribe { songs ->
|
||||
if (songs.size > 0) {
|
||||
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 ->
|
||||
getTopAlbums(context).subscribe { albums ->
|
||||
getTopAlbumsFlowable(context).subscribe { albums ->
|
||||
if (albums.size > 0) {
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
@ -40,5 +42,8 @@ public abstract class AbsCustomPlaylist extends Playlist {
|
|||
}
|
||||
|
||||
@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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
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 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 String name;
|
||||
|
||||
|
@ -32,6 +49,23 @@ public class Playlist implements Parcelable {
|
|||
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
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -59,7 +93,6 @@ public class Playlist implements Parcelable {
|
|||
'}';
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
@ -71,20 +104,5 @@ public class Playlist implements Parcelable {
|
|||
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.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
|
@ -51,7 +54,13 @@ public class HistoryPlaylist extends AbsSmartPlaylist {
|
|||
|
||||
@NonNull
|
||||
@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);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
|||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.loaders.LastAddedSongsLoader;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
|
@ -48,7 +51,13 @@ public class LastAddedPlaylist extends AbsSmartPlaylist {
|
|||
|
||||
@NonNull
|
||||
@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);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
|||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
|
@ -51,7 +54,13 @@ public class MyTopTracksPlaylist extends AbsSmartPlaylist {
|
|||
|
||||
@NonNull
|
||||
@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);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,12 @@ package code.name.monkey.retromusic.model.smartplaylist;
|
|||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
|
@ -47,7 +50,13 @@ public class ShuffleAllPlaylist extends AbsSmartPlaylist {
|
|||
|
||||
@NonNull
|
||||
@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);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.*
|
|||
*/
|
||||
|
||||
interface SearchContract {
|
||||
interface SearchView : BaseView<ArrayList<Any>>
|
||||
interface SearchView : BaseView<MutableList<Any>>
|
||||
|
||||
interface SearchPresenter : BasePresenter<SearchView> {
|
||||
fun search(query: String?)
|
||||
|
|
|
@ -34,9 +34,7 @@ class AlbumDetailsPresenter(private val view: AlbumDetailsContract.AlbumDetailsV
|
|||
}
|
||||
|
||||
override fun loadAlbumSongs(albumId: Int) {
|
||||
disposable.add(repository.getAlbum(albumId)
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.getAlbumFlowable(albumId)
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showAlbum(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -39,9 +39,7 @@ class AlbumPresenter(private val view: AlbumContract.AlbumView) : Presenter(), A
|
|||
}
|
||||
|
||||
override fun loadAlbums() {
|
||||
disposable.add(repository.allAlbums
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.allAlbumsFlowable
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showList(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -37,9 +37,7 @@ class ArtistDetailsPresenter(private val view: ArtistDetailContract.ArtistsDetai
|
|||
}
|
||||
|
||||
override fun loadArtistById() {
|
||||
disposable.add(repository.getArtistById(bundle.getInt(ArtistDetailActivity.EXTRA_ARTIST_ID).toLong())
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.getArtistByIdFlowable(bundle.getInt(ArtistDetailActivity.EXTRA_ARTIST_ID))
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showArtist(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -38,9 +38,7 @@ class ArtistPresenter(private val mView: ArtistContract.ArtistView) : Presenter(
|
|||
}
|
||||
|
||||
override fun loadArtists() {
|
||||
disposable.add(repository.allArtists
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.allArtistsFlowable
|
||||
.doOnSubscribe { mView.loading() }
|
||||
.subscribe({ this.showList(it) },
|
||||
{ mView.showEmptyView() },
|
||||
|
|
|
@ -36,9 +36,7 @@ class GenreDetailsPresenter(private val view: GenreDetailsContract.GenreDetailsV
|
|||
}
|
||||
|
||||
override fun loadGenre(genreId: Int) {
|
||||
disposable.add(repository.getGenre(genreId)
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.getGenreFlowable(genreId)
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showGenre(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -35,9 +35,7 @@ class GenrePresenter(
|
|||
}
|
||||
|
||||
override fun loadGenre() {
|
||||
disposable.add(repository.allGenres
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.allGenresFlowable
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showList(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -53,7 +53,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
|||
}
|
||||
|
||||
private fun loadRecentArtists() {
|
||||
disposable += repository.recentArtists
|
||||
disposable += repository.recentArtistsFlowable
|
||||
.subscribe({
|
||||
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))
|
||||
|
@ -63,7 +63,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
|||
}
|
||||
|
||||
private fun loadRecentAlbums() {
|
||||
disposable += repository.recentAlbums
|
||||
disposable += repository.recentAlbumsFlowable
|
||||
.subscribe({
|
||||
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))
|
||||
|
@ -73,7 +73,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
|||
}
|
||||
|
||||
private fun loadATopAlbums() {
|
||||
disposable += repository.topAlbums
|
||||
disposable += repository.topAlbumsFlowable
|
||||
.subscribe({
|
||||
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))
|
||||
|
@ -83,7 +83,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
|||
}
|
||||
|
||||
private fun loadTopArtists() {
|
||||
disposable += repository.topArtists
|
||||
disposable += repository.topArtistsFlowable
|
||||
.subscribe({
|
||||
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))
|
||||
|
@ -93,7 +93,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
|
|||
}
|
||||
|
||||
private fun loadFavorite() {
|
||||
disposable += repository.favoritePlaylist
|
||||
disposable += repository.favoritePlaylistFlowable
|
||||
.subscribe({
|
||||
if (it.isNotEmpty()) hashSet.add(Home(4, R.string.favorites, 0, it, PLAYLISTS, R.drawable.ic_favorite_white_24dp))
|
||||
view.showData(ArrayList(hashSet))
|
||||
|
|
|
@ -35,9 +35,7 @@ class PlaylistPresenter(private val view: PlaylistContract.PlaylistView) : Prese
|
|||
}
|
||||
|
||||
override fun loadPlaylists() {
|
||||
disposable.add(repository.allPlaylists
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.allPlaylistsFlowable
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showList(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -35,9 +35,7 @@ class PlaylistSongsPresenter(private val view: PlaylistSongsContract.PlaylistSon
|
|||
}
|
||||
|
||||
override fun loadSongs(playlist: Playlist) {
|
||||
disposable.add(repository.getPlaylistSongs(playlist)
|
||||
.subscribeOn(schedulerProvider.io())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.getPlaylistSongsFlowable(playlist)
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ songs -> view.showData(songs) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -42,13 +42,6 @@ class SearchPresenter(private val view: SearchContract.SearchView) : Presenter()
|
|||
}
|
||||
|
||||
override fun search(query: String?) {
|
||||
disposable.add(repository.search(query)
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showList(it) },
|
||||
{ view.showEmptyView() },
|
||||
{ view.completed() }))
|
||||
view.showData(repository.search(query))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,9 +26,7 @@ import java.util.*
|
|||
class SongPresenter(private val view: SongContract.SongView) : Presenter(), SongContract.Presenter {
|
||||
|
||||
override fun loadSongs() {
|
||||
disposable.add(repository.allSongs
|
||||
.subscribeOn(schedulerProvider.computation())
|
||||
.observeOn(schedulerProvider.ui())
|
||||
disposable.add(repository.allSongsFlowable
|
||||
.doOnSubscribe { view.loading() }
|
||||
.subscribe({ this.showList(it) },
|
||||
{ view.showEmptyView() },
|
||||
|
|
|
@ -22,14 +22,15 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import code.name.monkey.retromusic.util.FileUtil;
|
||||
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 static final String DATABASE_NAME = "blacklist.db";
|
||||
|
|
|
@ -20,13 +20,13 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore.Audio.AudioColumns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import code.name.monkey.retromusic.loaders.SongLoader;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
|
@ -187,17 +187,34 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Observable<ArrayList<Song>> getSavedOriginalPlayingQueue() {
|
||||
public ArrayList<Song> getSavedOriginalPlayingQueue() {
|
||||
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
|
||||
}
|
||||
|
||||
@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,
|
||||
null, null, null, null, null);
|
||||
return SongLoader.INSTANCE.getSongs(cursor);
|
||||
|
|
|
@ -18,113 +18,163 @@ import android.content.Context
|
|||
import code.name.monkey.retromusic.App
|
||||
import code.name.monkey.retromusic.loaders.*
|
||||
import code.name.monkey.retromusic.model.*
|
||||
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
|
||||
import code.name.monkey.retromusic.providers.interfaces.Repository
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
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)
|
||||
|
||||
override fun allSongs(): ArrayList<Song> {
|
||||
return SongLoader.getAllSongs(context)
|
||||
}
|
||||
|
||||
override val favoritePlaylistFlowable: Observable<ArrayList<Playlist>>
|
||||
get() = PlaylistLoader.getFavoritePlaylistFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
|
||||
override val allSongs: Observable<ArrayList<Song>>
|
||||
get() = SongLoader.getAllSongs(context)
|
||||
override val allSongsFlowable: Observable<ArrayList<Song>>
|
||||
get() = SongLoader.getAllSongsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val suggestionSongs: Observable<ArrayList<Song>>
|
||||
override val suggestionSongsFlowable: Observable<ArrayList<Song>>
|
||||
get() = SongLoader.suggestSongs(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val allAlbums: Observable<ArrayList<Album>>
|
||||
get() = AlbumLoader.getAllAlbums(context)
|
||||
override val allAlbumsFlowable: Observable<ArrayList<Album>>
|
||||
get() = AlbumLoader.getAllAlbumsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val recentAlbums: Observable<ArrayList<Album>>
|
||||
get() = LastAddedSongsLoader.getLastAddedAlbums(context)
|
||||
override val recentAlbumsFlowable: Observable<ArrayList<Album>>
|
||||
get() = LastAddedSongsLoader.getLastAddedAlbumsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val topAlbums: Observable<ArrayList<Album>>
|
||||
get() = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
|
||||
override val topAlbumsFlowable: Observable<ArrayList<Album>>
|
||||
get() = TopAndRecentlyPlayedTracksLoader.getTopAlbumsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val allArtists: Observable<ArrayList<Artist>>
|
||||
get() = ArtistLoader.getAllArtists(context)
|
||||
override val allArtistsFlowable: Observable<ArrayList<Artist>>
|
||||
get() = ArtistLoader.getAllArtistsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val recentArtists: Observable<ArrayList<Artist>>
|
||||
get() = LastAddedSongsLoader.getLastAddedArtists(context)
|
||||
override val recentArtistsFlowable: Observable<ArrayList<Artist>>
|
||||
get() = LastAddedSongsLoader.getLastAddedArtistsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val topArtists: Observable<ArrayList<Artist>>
|
||||
get() = TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
|
||||
override val topArtistsFlowable: Observable<ArrayList<Artist>>
|
||||
get() = TopAndRecentlyPlayedTracksLoader.getTopArtistsFlowable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val allPlaylists: Observable<ArrayList<Playlist>>
|
||||
get() = PlaylistLoader.getAllPlaylists(context)
|
||||
override val allPlaylistsFlowable: Observable<ArrayList<Playlist>>
|
||||
get() = PlaylistLoader.getAllPlaylistsFlowoable(context)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.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())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
override val allThings: Observable<ArrayList<AbsSmartPlaylist>>
|
||||
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> {
|
||||
override fun getSong(id: Int): Song {
|
||||
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)
|
||||
.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())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
override fun search(query: String?): Observable<ArrayList<Any>> {
|
||||
return SearchLoader.searchAll(context, query)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
override fun getPlaylistSongs(playlist: Playlist): Observable<ArrayList<Song>> {
|
||||
override fun getPlaylistSongs(playlist: Playlist): ArrayList<Song> {
|
||||
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)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,9 +15,7 @@
|
|||
package code.name.monkey.retromusic.providers.interfaces
|
||||
|
||||
import code.name.monkey.retromusic.model.*
|
||||
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
|
||||
import io.reactivex.Observable
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Created by hemanths on 11/08/17.
|
||||
|
@ -25,42 +23,68 @@ import kotlin.collections.ArrayList
|
|||
|
||||
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 androidx.core.content.ContextCompat
|
||||
import code.name.monkey.retromusic.BuildConfig
|
||||
import code.name.monkey.retromusic.Constants.ACTION_PAUSE
|
||||
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
|
||||
import code.name.monkey.retromusic.service.MusicService.*
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
|
@ -31,15 +30,15 @@ import android.media.AudioManager;
|
|||
import android.media.audiofx.AudioEffect;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
|
@ -50,10 +49,10 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media.MediaBrowserServiceCompat;
|
||||
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.AppWidgetSmall;
|
||||
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.GlideApp;
|
||||
import code.name.monkey.retromusic.glide.GlideRequest;
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension;
|
||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget;
|
||||
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.Song;
|
||||
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.playback.Playback;
|
||||
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.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
|
||||
*/
|
||||
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 RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic";
|
||||
public static final String MUSIC_PACKAGE_NAME = "com.android.music";
|
||||
|
||||
public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause";
|
||||
public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play";
|
||||
public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist";
|
||||
public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause";
|
||||
public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop";
|
||||
public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip";
|
||||
public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind";
|
||||
public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice";
|
||||
public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice";
|
||||
public static final String INTENT_EXTRA_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist";
|
||||
public static final String INTENT_EXTRA_SHUFFLE_MODE = RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode";
|
||||
|
||||
public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate";
|
||||
public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name";
|
||||
|
||||
// Do not change these three strings as it will break support with other apps (e.g. last.fm scrobbling)
|
||||
public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged";
|
||||
public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged";
|
||||
public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged";
|
||||
|
||||
public static final String FAVORITE_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged";
|
||||
|
||||
public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged";
|
||||
public static final String SHUFFLE_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged";
|
||||
public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged";
|
||||
|
||||
public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat";
|
||||
public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle";
|
||||
public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite";
|
||||
|
||||
public static final String SAVED_POSITION = "POSITION";
|
||||
public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK";
|
||||
public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE";
|
||||
public static final String SAVED_REPEAT_MODE = "REPEAT_MODE";
|
||||
|
||||
public static final int RELEASE_WAKELOCK = 0;
|
||||
public static final int TRACK_ENDED = 1;
|
||||
public static final int TRACK_WENT_TO_NEXT = 2;
|
||||
public static final int PLAY_SONG = 3;
|
||||
public static final int PREPARE_NEXT = 4;
|
||||
public static final int SET_POSITION = 5;
|
||||
public static final int FOCUS_CHANGE = 6;
|
||||
public static final int DUCK = 7;
|
||||
public static final int UNDUCK = 8;
|
||||
public static final int RESTORE_QUEUES = 9;
|
||||
|
||||
public static final int SHUFFLE_MODE_NONE = 0;
|
||||
public static final int SHUFFLE_MODE_SHUFFLE = 1;
|
||||
|
||||
public static final int REPEAT_MODE_NONE = 0;
|
||||
public static final int REPEAT_MODE_ALL = 1;
|
||||
public static final int REPEAT_MODE_THIS = 2;
|
||||
|
||||
public static final int SAVE_QUEUES = 0;
|
||||
private static final 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
|
||||
| PlaybackStateCompat.ACTION_PAUSE
|
||||
| PlaybackStateCompat.ACTION_PLAY_PAUSE
|
||||
|
@ -143,6 +157,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
| PlaybackStateCompat.ACTION_SEEK_TO;
|
||||
private final IBinder musicBind = new MusicBinder();
|
||||
public boolean pendingQuit = false;
|
||||
public Playback playback;
|
||||
public int position = -1;
|
||||
public int nextPosition = -1;
|
||||
private AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance();
|
||||
private AppWidgetClassic appWidgetClassic = AppWidgetClassic.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> originalPlayingQueue = new ArrayList<>();
|
||||
private int position = -1;
|
||||
private int nextPosition = -1;
|
||||
private int shuffleMode;
|
||||
private int repeatMode;
|
||||
private boolean queuesRestored;
|
||||
|
@ -198,7 +212,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
};
|
||||
private PlayingNotification playingNotification;
|
||||
private AudioManager audioManager;
|
||||
@SuppressWarnings("deprecation")
|
||||
private MediaSessionCompat mediaSession;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private PlaybackHandler playerHandler;
|
||||
|
@ -235,7 +248,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
super.onCallStateChanged(state, incomingNumber);
|
||||
}
|
||||
};
|
||||
private boolean isServiceBound;
|
||||
private Handler uiThreadHandler;
|
||||
private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
||||
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) {
|
||||
return MusicUtil.getSongFileUri(song.getId()).toString();
|
||||
|
@ -278,7 +292,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
@ -314,8 +327,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
|
||||
initNotification();
|
||||
|
||||
mediaStoreObserver = new MediaStoreObserver(playerHandler);
|
||||
throttledSeekHandler = new ThrottledSeekHandler(playerHandler);
|
||||
mediaStoreObserver = new MediaStoreObserver(this, playerHandler);
|
||||
throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler);
|
||||
getContentResolver().registerContentObserver(
|
||||
MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver);
|
||||
getContentResolver().registerContentObserver(
|
||||
|
@ -324,14 +337,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this);
|
||||
|
||||
restoreState();
|
||||
|
||||
mediaSession.setActive(true);
|
||||
|
||||
mPackageValidator = new PackageValidator(this, R.xml.allowed_media_browser_callers);
|
||||
mMusicProvider = new AutoMusicProvider(this);
|
||||
sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED"));
|
||||
|
||||
registerHeadsetEvents();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private AudioManager getAudioManager() {
|
||||
|
@ -354,50 +364,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
"RetroMusicPlayer",
|
||||
mediaButtonReceiverComponentName,
|
||||
mediaButtonReceiverPendingIntent);
|
||||
mediaSession.setCallback(new MediaSessionCompat.Callback() {
|
||||
|
||||
@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);
|
||||
}
|
||||
});
|
||||
|
||||
MediaSessionCallback mediasessionCallback = new MediaSessionCallback(getApplicationContext(), this);
|
||||
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
|
||||
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
|
||||
);
|
||||
|
||||
mediaSession.setCallback(mediasessionCallback);
|
||||
mediaSession.setActive(true);
|
||||
mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent);
|
||||
setSessionToken(mediaSession.getSessionToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -437,6 +411,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
case ACTION_PENDING_QUIT:
|
||||
pendingQuit = true;
|
||||
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) {
|
||||
Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST);
|
||||
int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode());
|
||||
|
||||
if (playlist != null) {
|
||||
if (playlist instanceof AbsCustomPlaylist) {
|
||||
((AbsCustomPlaylist) playlist).getSongs(getApplicationContext())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(songs -> {
|
||||
playSongs(shuffleMode, songs);
|
||||
}, throwable -> {
|
||||
});
|
||||
ArrayList<Song> playlistSongs = playlist.getSongs(getApplicationContext());
|
||||
if (!playlistSongs.isEmpty()) {
|
||||
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
|
||||
int startPosition = 0;
|
||||
if (!playlistSongs.isEmpty()) {
|
||||
startPosition = new Random().nextInt(playlistSongs.size());
|
||||
}
|
||||
openQueue(playlistSongs, startPosition, true);
|
||||
setShuffleMode(shuffleMode);
|
||||
} else {
|
||||
PlaylistSongsLoader.INSTANCE.getPlaylistSongList(getApplicationContext(), playlist.id)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(songs -> {
|
||||
playSongs(shuffleMode, songs);
|
||||
}, throwable -> {
|
||||
});
|
||||
openQueue(playlistSongs, 0, true);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show();
|
||||
|
@ -507,25 +483,43 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
isServiceBound = true;
|
||||
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
|
||||
public void onRebind(Intent intent) {
|
||||
isServiceBound = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
isServiceBound = false;
|
||||
if (!isPlaying()) {
|
||||
stopSelf();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveQueuesImpl() {
|
||||
public void saveQueuesImpl() {
|
||||
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();
|
||||
}
|
||||
|
||||
private void savePositionInTrack() {
|
||||
public void savePositionInTrack() {
|
||||
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);
|
||||
}
|
||||
|
||||
private synchronized void restoreQueuesAndPositionIfNecessary() {
|
||||
public synchronized void restoreQueuesAndPositionIfNecessary() {
|
||||
if (!queuesRestored && playingQueue.isEmpty()) {
|
||||
ArrayList<Song> restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue()
|
||||
.blockingFirst();
|
||||
|
||||
ArrayList<Song> restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue()
|
||||
.blockingFirst();
|
||||
|
||||
ArrayList<Song> restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue();
|
||||
ArrayList<Song> restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue();
|
||||
int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1);
|
||||
int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1);
|
||||
|
||||
|
@ -587,20 +577,16 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
queuesRestored = true;
|
||||
}
|
||||
|
||||
private int quit() {
|
||||
public void quit() {
|
||||
pause();
|
||||
playingNotification.stop();
|
||||
|
||||
if (isServiceBound) {
|
||||
return START_STICKY;
|
||||
} else {
|
||||
closeAudioEffectSession();
|
||||
getAudioManager().abandonAudioFocus(audioFocusListener);
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void releaseResources() {
|
||||
playerHandler.removeCallbacksAndMessages(null);
|
||||
musicPlayerHandlerThread.quitSafely();
|
||||
|
@ -629,7 +615,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
playSongAt(getNextPosition(force));
|
||||
}
|
||||
|
||||
private boolean openTrackAndPrepareNextAt(int position) {
|
||||
public boolean openTrackAndPrepareNextAt(int position) {
|
||||
synchronized (this) {
|
||||
this.position = position;
|
||||
boolean prepared = openCurrent();
|
||||
|
@ -655,7 +641,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget();
|
||||
}
|
||||
|
||||
private boolean prepareNextImpl() {
|
||||
public boolean prepareNextImpl() {
|
||||
synchronized (this) {
|
||||
try {
|
||||
int nextPosition = getNextPosition(false);
|
||||
|
@ -694,13 +680,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
}
|
||||
}
|
||||
|
||||
private void updateMediaSessionPlaybackState() {
|
||||
mediaSession.setPlaybackState(
|
||||
new PlaybackStateCompat.Builder()
|
||||
public void updateMediaSessionPlaybackState() {
|
||||
PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder()
|
||||
.setActions(MEDIA_SESSION_ACTIONS)
|
||||
.setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED,
|
||||
getSongProgressMillis(), 1)
|
||||
.build());
|
||||
getSongProgressMillis(), 1);
|
||||
|
||||
|
||||
mediaSession.setPlaybackState(stateBuilder.build());
|
||||
}
|
||||
|
||||
private void updateMediaSessionMetaData() {
|
||||
|
@ -801,7 +788,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
return position;
|
||||
}
|
||||
|
||||
private boolean isLastTrack() {
|
||||
public boolean isLastTrack() {
|
||||
return getPosition() == getPlayingQueue().size() - 1;
|
||||
}
|
||||
|
||||
|
@ -946,7 +933,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget();
|
||||
}
|
||||
|
||||
private void playSongAtImpl(int position) {
|
||||
public void playSongAtImpl(int position) {
|
||||
if (openTrackAndPrepareNextAt(position)) {
|
||||
play();
|
||||
} else {
|
||||
|
@ -1128,18 +1115,18 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
notifyChange(QUEUE_CHANGED);
|
||||
}
|
||||
|
||||
private void notifyChange(@NonNull final String what) {
|
||||
public void notifyChange(@NonNull final String what) {
|
||||
handleAndSendChangeInternal(what);
|
||||
sendPublicIntent(what);
|
||||
}
|
||||
|
||||
private void handleAndSendChangeInternal(@NonNull final String what) {
|
||||
public void handleAndSendChangeInternal(@NonNull final String what) {
|
||||
handleChangeInternal(what);
|
||||
sendChangeInternal(what);
|
||||
}
|
||||
|
||||
// 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 Song song = getCurrentSong();
|
||||
|
@ -1266,187 +1253,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
playerHandler.sendEmptyMessage(TRACK_ENDED);
|
||||
}
|
||||
|
||||
|
||||
private static final class QueueSaveHandler extends Handler {
|
||||
@NonNull
|
||||
private final WeakReference<MusicService> mService;
|
||||
|
||||
QueueSaveHandler(final MusicService service, @NonNull final Looper looper) {
|
||||
super(looper);
|
||||
mService = new WeakReference<>(service);
|
||||
public boolean isPausedByTransientLossOfFocus() {
|
||||
return pausedByTransientLossOfFocus;
|
||||
}
|
||||
|
||||
@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 {
|
||||
@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 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 void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) {
|
||||
this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus;
|
||||
}
|
||||
|
||||
public class MusicBinder extends Binder {
|
||||
|
@ -1455,52 +1267,4 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
|
|||
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.service.media.MediaBrowserService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
|
||||
import code.name.monkey.retromusic.loaders.AlbumLoader;
|
||||
|
@ -194,7 +195,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
} else {
|
||||
switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) {
|
||||
case TYPE_ARTIST:
|
||||
List<Artist> artistList = ArtistLoader.INSTANCE.getAllArtists(mContext).blockingFirst();
|
||||
List<Artist> artistList = ArtistLoader.INSTANCE.getAllArtists(mContext) ;
|
||||
for (Artist artist : artistList) {
|
||||
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");
|
||||
|
@ -215,7 +216,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
Uri.parse("android.resource://code.name.monkey.retromusic/drawable/default_artist_art"),
|
||||
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) {
|
||||
String songCount = String.format("%d %s", album.getSongs().size(), album.getSongs().size() > 1 ? "Songs" : "Song");
|
||||
fillMediaItems(mediaItems,
|
||||
|
@ -227,7 +228,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
}
|
||||
break;
|
||||
case TYPE_ALBUM:
|
||||
List<Album> albumList = AlbumLoader.Companion.getAllAlbums(mContext).blockingFirst();
|
||||
List<Album> albumList = AlbumLoader.INSTANCE.getAllAlbums(mContext);
|
||||
for (Album album : albumList) {
|
||||
fillMediaItems(mediaItems,
|
||||
Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.getId()),
|
||||
|
@ -238,7 +239,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
}
|
||||
break;
|
||||
case TYPE_SONG:
|
||||
List<Song> songList = SongLoader.INSTANCE.getAllSongs(mContext).blockingFirst();
|
||||
List<Song> songList = SongLoader.INSTANCE.getAllSongs(mContext);
|
||||
for (Song song : songList) {
|
||||
fillMediaItems(mediaItems,
|
||||
String.valueOf(song.getId()),
|
||||
|
@ -250,7 +251,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
break;
|
||||
|
||||
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) {
|
||||
fillMediaItems(mediaItems,
|
||||
String.valueOf(song.getId()),
|
||||
|
@ -261,7 +262,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
}
|
||||
break;
|
||||
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) {
|
||||
fillMediaItems(mediaItems,
|
||||
String.valueOf(song.getId()),
|
||||
|
@ -272,9 +273,9 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
}
|
||||
break;
|
||||
case TYPE_PLAYLIST:
|
||||
List<Playlist> playlistList = PlaylistLoader.INSTANCE.getAllPlaylists(mContext).blockingFirst();
|
||||
List<Playlist> playlistList = PlaylistLoader.INSTANCE.getAllPlaylists(mContext);
|
||||
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");
|
||||
fillMediaItems(mediaItems,
|
||||
Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id),
|
||||
|
@ -285,7 +286,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
}
|
||||
break;
|
||||
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) {
|
||||
fillMediaItems(mediaItems,
|
||||
String.valueOf(song.getId()),
|
||||
|
@ -326,7 +327,7 @@ public class WearBrowserService extends MediaBrowserService {
|
|||
long songId = Long.parseLong(mediaId);
|
||||
setSessionActive();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,6 @@ import android.widget.RemoteViews
|
|||
import androidx.core.app.NotificationCompat
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
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.activities.MainActivity
|
||||
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.palette.BitmapPaletteWrapper
|
||||
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.RetroColorUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
|
|
|
@ -24,17 +24,15 @@ import android.graphics.drawable.Drawable
|
|||
import android.os.Build
|
||||
import android.text.Html
|
||||
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.activities.MainActivity
|
||||
import code.name.monkey.retromusic.glide.GlideApp
|
||||
import code.name.monkey.retromusic.glide.RetroGlideExtension
|
||||
import code.name.monkey.retromusic.glide.RetroSimpleTarget
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
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.RetroColorUtil
|
||||
import com.bumptech.glide.request.target.Target
|
||||
|
@ -48,12 +46,18 @@ class PlayingNotificationImpl24 : PlayingNotification() {
|
|||
|
||||
val song = service.currentSong
|
||||
val isPlaying = service.isPlaying
|
||||
|
||||
val isFavorite = MusicUtil.isFavorite(service, song)
|
||||
val playButtonResId = if (isPlaying)
|
||||
R.drawable.ic_pause_white_24dp
|
||||
else
|
||||
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)
|
||||
action.putExtra("expand", true)
|
||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
|
@ -93,6 +97,12 @@ class PlayingNotificationImpl24 : PlayingNotification() {
|
|||
if (bitmapFinal == null) {
|
||||
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(
|
||||
playButtonResId,
|
||||
service.getString(R.string.action_play_pause),
|
||||
|
@ -114,7 +124,7 @@ class PlayingNotificationImpl24 : PlayingNotification() {
|
|||
retrievePlaybackAction(ACTION_SKIP))
|
||||
|
||||
val builder = NotificationCompat.Builder(service,
|
||||
PlayingNotification.NOTIFICATION_CHANNEL_ID)
|
||||
NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setLargeIcon(bitmapFinal)
|
||||
.setContentIntent(clickIntent)
|
||||
|
|
|
@ -25,10 +25,6 @@ import android.widget.RemoteViews
|
|||
import androidx.core.app.NotificationCompat
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
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.activities.MainActivity
|
||||
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.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.RetroUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil.createBitmap
|
||||
|
@ -155,14 +152,14 @@ class PlayingNotificationOreo : PlayingNotification() {
|
|||
val primary = MaterialValueHelper.getPrimaryTextColor(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 prev = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, primary)!!, PlayingNotification.NOTIFICATION_CONTROLS_SIZE_MULTIPLIER)
|
||||
val next = createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_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)!!, 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,
|
||||
if (isPlaying)
|
||||
R.drawable.ic_pause_white_24dp
|
||||
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.subtitle, secondary)
|
||||
|
|
|
@ -20,6 +20,9 @@ import android.os.Environment;
|
|||
import android.provider.MediaStore;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -34,12 +37,9 @@ import java.util.Collections;
|
|||
import java.util.LinkedList;
|
||||
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.SortedCursor;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
|
||||
public final class FileUtil {
|
||||
|
@ -57,8 +57,9 @@ public final class FileUtil {
|
|||
stream.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Observable<ArrayList<Song>> matchFilesWithMediaStore(@NonNull Context context,
|
||||
public static ArrayList<Song> matchFilesWithMediaStore(@NonNull Context context,
|
||||
@Nullable List<File> files) {
|
||||
return SongLoader.INSTANCE.getSongs(makeSongCursor(context, files));
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
package code.name.monkey.retromusic.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
|
@ -21,13 +23,22 @@ import android.graphics.Matrix;
|
|||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.ExifInterface;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import code.name.monkey.appthemehelper.util.TintHelper;
|
||||
|
||||
/**
|
||||
* Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub :
|
||||
* https://github.com/zetbaitsu
|
||||
|
@ -57,6 +68,37 @@ public class ImageUtil {
|
|||
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}.
|
||||
*/
|
||||
|
|
|
@ -51,7 +51,6 @@ import code.name.monkey.retromusic.model.Genre;
|
|||
import code.name.monkey.retromusic.model.Playlist;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
|
||||
public class MusicUtil {
|
||||
|
@ -303,7 +302,7 @@ public class MusicUtil {
|
|||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
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);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
|
@ -404,9 +403,9 @@ public class MusicUtil {
|
|||
|
||||
public static void toggleFavorite(@NonNull final Context context, @NonNull final Song song) {
|
||||
if (isFavorite(context, song)) {
|
||||
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).blockingFirst().id);
|
||||
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).id);
|
||||
} else {
|
||||
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).blockingFirst().id,
|
||||
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).id,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
@ -416,18 +415,18 @@ public class MusicUtil {
|
|||
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));
|
||||
}
|
||||
|
||||
private static Observable<Playlist> getOrCreateFavoritesPlaylist(@NonNull final Context context) {
|
||||
private static Playlist getOrCreateFavoritesPlaylist(@NonNull final Context context) {
|
||||
return PlaylistLoader.INSTANCE.getPlaylist(context,
|
||||
PlaylistsUtil.createPlaylist(context, context.getString(R.string.favorites)));
|
||||
}
|
||||
|
||||
public static boolean isFavorite(@NonNull final Context context, @NonNull final Song song) {
|
||||
return PlaylistsUtil
|
||||
.doPlaylistContains(context, getFavoritesPlaylist(context).blockingFirst().id, song.getId());
|
||||
.doPlaylistContains(context, getFavoritesPlaylist(context).id, song.getId());
|
||||
}
|
||||
|
||||
public static boolean isArtistNameUnknown(@Nullable String artistName) {
|
||||
|
@ -475,6 +474,15 @@ public class MusicUtil {
|
|||
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
|
||||
public static String getYearString(int 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.PlaylistSong;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
||||
|
||||
|
@ -262,7 +261,9 @@ public class PlaylistsUtil {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,10 +61,8 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import code.name.monkey.appthemehelper.ThemeStore;
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil;
|
||||
import code.name.monkey.appthemehelper.util.TintHelper;
|
||||
import code.name.monkey.retromusic.App;
|
||||
import code.name.monkey.retromusic.R;
|
||||
|
||||
public class RetroUtil {
|
||||
|
||||
|
@ -176,6 +174,7 @@ public class RetroUtil {
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId,
|
||||
@Nullable Resources.Theme theme) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
|
@ -184,11 +183,11 @@ public class RetroUtil {
|
|||
return VectorDrawableCompat.create(res, resId, theme);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id,
|
||||
@ColorInt int color) {
|
||||
return TintHelper
|
||||
.createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()),
|
||||
color);
|
||||
return TintHelper.createTintedDrawable(
|
||||
getVectorDrawable(context.getResources(), id, context.getTheme()), color);
|
||||
}
|
||||
|
||||
public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int id,
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
tools:ignore="MissingPrefix"
|
||||
tools:text="Title" />
|
||||
|
||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
||||
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||
android:id="@+id/text"
|
||||
style="@style/TextAppearance.MaterialComponents.Subtitle2"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
tools:ignore="MissingPrefix"
|
||||
tools:text="Title" />
|
||||
|
||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
||||
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||
android:id="@+id/text"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
tools:ignore="MissingPrefix"
|
||||
tools:text="Title" />
|
||||
|
||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
||||
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||
android:id="@+id/text"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
tools:ignore="MissingPrefix"
|
||||
tools:text="Title" />
|
||||
|
||||
<code.name.monkey.appthemehelper.common.views.ATEPrimaryTextView
|
||||
<code.name.monkey.appthemehelper.common.views.ATESecondaryTextView
|
||||
android:id="@+id/text"
|
||||
style="@style/TextAppearance.MaterialComponents.Subtitle2"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -650,4 +650,14 @@
|
|||
<string name="saf_guide_slide3_description">Do not open any sub-folders</string>
|
||||
<string name="deleting_songs">Deleting songs</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>
|
||||
|
|
|
@ -150,4 +150,12 @@
|
|||
<style name="AppTextField" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
|
||||
<item name="boxBackgroundColor">@color/text_field_background</item>
|
||||
</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>
|
||||
|
|
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