Fixed lint errors

Added comments
This commit is contained in:
Nite 2021-07-18 13:17:29 +02:00
parent cf05d3c781
commit 982639d2c7
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
13 changed files with 385 additions and 249 deletions

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.moire.ultrasonic"
android:installLocation="auto">
xmlns:tools="http://schemas.android.com/tools"
package="org.moire.ultrasonic"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@ -60,6 +61,7 @@
</service>
<service
tools:ignore="ExportedService"
android:name=".service.AutoMediaBrowserService"
android:label="@string/common.appname"
android:exported="true">

View File

@ -1,6 +1,5 @@
package org.moire.ultrasonic.di
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import org.moire.ultrasonic.service.AudioFocusHandler
import org.moire.ultrasonic.service.DownloadQueueSerializer

View File

@ -1,3 +1,10 @@
/*
* AutoMediaBrowserService.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.service
import android.os.Bundle
@ -24,44 +31,45 @@ import org.moire.ultrasonic.util.MediaSessionHandler
import org.moire.ultrasonic.util.Util
import timber.log.Timber
const val MEDIA_ROOT_ID = "MEDIA_ROOT_ID"
const val MEDIA_ALBUM_ID = "MEDIA_ALBUM_ID"
const val MEDIA_ALBUM_PAGE_ID = "MEDIA_ALBUM_PAGE_ID"
const val MEDIA_ALBUM_NEWEST_ID = "MEDIA_ALBUM_NEWEST_ID"
const val MEDIA_ALBUM_RECENT_ID = "MEDIA_ALBUM_RECENT_ID"
const val MEDIA_ALBUM_FREQUENT_ID = "MEDIA_ALBUM_FREQUENT_ID"
const val MEDIA_ALBUM_RANDOM_ID = "MEDIA_ALBUM_RANDOM_ID"
const val MEDIA_ALBUM_STARRED_ID = "MEDIA_ALBUM_STARRED_ID"
const val MEDIA_SONG_RANDOM_ID = "MEDIA_SONG_RANDOM_ID"
const val MEDIA_SONG_STARRED_ID = "MEDIA_SONG_STARRED_ID"
const val MEDIA_ARTIST_ID = "MEDIA_ARTIST_ID"
const val MEDIA_LIBRARY_ID = "MEDIA_LIBRARY_ID"
const val MEDIA_PLAYLIST_ID = "MEDIA_PLAYLIST_ID"
const val MEDIA_SHARE_ID = "MEDIA_SHARE_ID"
const val MEDIA_BOOKMARK_ID = "MEDIA_BOOKMARK_ID"
const val MEDIA_PODCAST_ID = "MEDIA_PODCAST_ID"
const val MEDIA_ALBUM_ITEM = "MEDIA_ALBUM_ITEM"
const val MEDIA_PLAYLIST_SONG_ITEM = "MEDIA_PLAYLIST_SONG_ITEM"
const val MEDIA_PLAYLIST_ITEM = "MEDIA_PLAYLIST_ITEM"
const val MEDIA_ARTIST_ITEM = "MEDIA_ARTIST_ITEM"
const val MEDIA_ARTIST_SECTION = "MEDIA_ARTIST_SECTION"
const val MEDIA_ALBUM_SONG_ITEM = "MEDIA_ALBUM_SONG_ITEM"
const val MEDIA_SONG_STARRED_ITEM = "MEDIA_SONG_STARRED_ITEM"
const val MEDIA_SONG_RANDOM_ITEM = "MEDIA_SONG_RANDOM_ITEM"
const val MEDIA_SHARE_ITEM = "MEDIA_SHARE_ITEM"
const val MEDIA_SHARE_SONG_ITEM = "MEDIA_SHARE_SONG_ITEM"
const val MEDIA_BOOKMARK_ITEM = "MEDIA_BOOKMARK_ITEM"
const val MEDIA_PODCAST_ITEM = "MEDIA_PODCAST_ITEM"
const val MEDIA_PODCAST_EPISODE_ITEM = "MEDIA_PODCAST_EPISODE_ITEM"
const val MEDIA_SEARCH_SONG_ITEM = "MEDIA_SEARCH_SONG_ITEM"
private const val MEDIA_ROOT_ID = "MEDIA_ROOT_ID"
private const val MEDIA_ALBUM_ID = "MEDIA_ALBUM_ID"
private const val MEDIA_ALBUM_PAGE_ID = "MEDIA_ALBUM_PAGE_ID"
private const val MEDIA_ALBUM_NEWEST_ID = "MEDIA_ALBUM_NEWEST_ID"
private const val MEDIA_ALBUM_RECENT_ID = "MEDIA_ALBUM_RECENT_ID"
private const val MEDIA_ALBUM_FREQUENT_ID = "MEDIA_ALBUM_FREQUENT_ID"
private const val MEDIA_ALBUM_RANDOM_ID = "MEDIA_ALBUM_RANDOM_ID"
private const val MEDIA_ALBUM_STARRED_ID = "MEDIA_ALBUM_STARRED_ID"
private const val MEDIA_SONG_RANDOM_ID = "MEDIA_SONG_RANDOM_ID"
private const val MEDIA_SONG_STARRED_ID = "MEDIA_SONG_STARRED_ID"
private const val MEDIA_ARTIST_ID = "MEDIA_ARTIST_ID"
private const val MEDIA_LIBRARY_ID = "MEDIA_LIBRARY_ID"
private const val MEDIA_PLAYLIST_ID = "MEDIA_PLAYLIST_ID"
private const val MEDIA_SHARE_ID = "MEDIA_SHARE_ID"
private const val MEDIA_BOOKMARK_ID = "MEDIA_BOOKMARK_ID"
private const val MEDIA_PODCAST_ID = "MEDIA_PODCAST_ID"
private const val MEDIA_ALBUM_ITEM = "MEDIA_ALBUM_ITEM"
private const val MEDIA_PLAYLIST_SONG_ITEM = "MEDIA_PLAYLIST_SONG_ITEM"
private const val MEDIA_PLAYLIST_ITEM = "MEDIA_PLAYLIST_ITEM"
private const val MEDIA_ARTIST_ITEM = "MEDIA_ARTIST_ITEM"
private const val MEDIA_ARTIST_SECTION = "MEDIA_ARTIST_SECTION"
private const val MEDIA_ALBUM_SONG_ITEM = "MEDIA_ALBUM_SONG_ITEM"
private const val MEDIA_SONG_STARRED_ITEM = "MEDIA_SONG_STARRED_ITEM"
private const val MEDIA_SONG_RANDOM_ITEM = "MEDIA_SONG_RANDOM_ITEM"
private const val MEDIA_SHARE_ITEM = "MEDIA_SHARE_ITEM"
private const val MEDIA_SHARE_SONG_ITEM = "MEDIA_SHARE_SONG_ITEM"
private const val MEDIA_BOOKMARK_ITEM = "MEDIA_BOOKMARK_ITEM"
private const val MEDIA_PODCAST_ITEM = "MEDIA_PODCAST_ITEM"
private const val MEDIA_PODCAST_EPISODE_ITEM = "MEDIA_PODCAST_EPISODE_ITEM"
private const val MEDIA_SEARCH_SONG_ITEM = "MEDIA_SEARCH_SONG_ITEM"
// Currently the display limit for long lists is 100 items
const val displayLimit = 100
const val searchLimit = 10
private const val DISPLAY_LIMIT = 100
private const val SEARCH_LIMIT = 10
/**
* MediaBrowserService implementation for e.g. Android Auto
*/
@Suppress("TooManyFunctions", "LargeClass")
class AutoMediaBrowserService : MediaBrowserServiceCompat() {
private lateinit var mediaSessionEventListener: MediaSessionEventListener
@ -84,6 +92,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
private val useId3Tags get() = Util.getShouldUseId3Tags()
private val musicFolderId get() = activeServerProvider.getActiveServer().musicFolderId
@Suppress("MagicNumber")
override fun onCreate() {
super.onCreate()
@ -95,16 +104,23 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
override fun onPlayFromMediaIdRequested(mediaId: String?, extras: Bundle?) {
Timber.d("AutoMediaBrowserService onPlayFromMediaIdRequested called. mediaId: %s", mediaId)
Timber.d(
"AutoMediaBrowserService onPlayFromMediaIdRequested called. mediaId: %s",
mediaId
)
if (mediaId == null) return
val mediaIdParts = mediaId.split('|')
when (mediaIdParts.first()) {
MEDIA_PLAYLIST_ITEM -> playPlaylist(mediaIdParts[1], mediaIdParts[2])
MEDIA_PLAYLIST_SONG_ITEM -> playPlaylistSong(mediaIdParts[1], mediaIdParts[2], mediaIdParts[3])
MEDIA_PLAYLIST_SONG_ITEM -> playPlaylistSong(
mediaIdParts[1], mediaIdParts[2], mediaIdParts[3]
)
MEDIA_ALBUM_ITEM -> playAlbum(mediaIdParts[1], mediaIdParts[2])
MEDIA_ALBUM_SONG_ITEM -> playAlbumSong(mediaIdParts[1], mediaIdParts[2], mediaIdParts[3])
MEDIA_ALBUM_SONG_ITEM -> playAlbumSong(
mediaIdParts[1], mediaIdParts[2], mediaIdParts[3]
)
MEDIA_SONG_STARRED_ID -> playStarredSongs()
MEDIA_SONG_STARRED_ITEM -> playStarredSong(mediaIdParts[1])
MEDIA_SONG_RANDOM_ID -> playRandomSongs()
@ -113,7 +129,9 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
MEDIA_SHARE_SONG_ITEM -> playShareSong(mediaIdParts[1], mediaIdParts[2])
MEDIA_BOOKMARK_ITEM -> playBookmark(mediaIdParts[1])
MEDIA_PODCAST_ITEM -> playPodcast(mediaIdParts[1])
MEDIA_PODCAST_EPISODE_ITEM -> playPodcastEpisode(mediaIdParts[1], mediaIdParts[2])
MEDIA_PODCAST_EPISODE_ITEM -> playPodcastEpisode(
mediaIdParts[1], mediaIdParts[2]
)
MEDIA_SEARCH_SONG_ITEM -> playSearch(mediaIdParts[1])
}
}
@ -123,7 +141,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
if (query.isNullOrBlank()) playRandomSongs()
serviceScope.launch {
val criteria = SearchCriteria(query!!, 0, 0, displayLimit)
val criteria = SearchCriteria(query!!, 0, 0, DISPLAY_LIMIT)
val searchResult = callWithErrorHandling { musicService.search(criteria) }
// Try to find the best match
@ -146,12 +164,17 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
mediaSessionHandler.initialize()
val handler = Handler()
handler.postDelayed({
// Ultrasonic may be started from Android Auto. This boots up the necessary components.
Timber.d("AutoMediaBrowserService starting lifecycleSupport and MediaPlayerService...")
lifecycleSupport.onCreate()
MediaPlayerService.getInstance()
}, 100)
handler.postDelayed(
{
// Ultrasonic may be started from Android Auto. This boots up the necessary components.
Timber.d(
"AutoMediaBrowserService starting lifecycleSupport and MediaPlayerService..."
)
lifecycleSupport.onCreate()
MediaPlayerService.getInstance()
},
100
)
Timber.i("AutoMediaBrowserService onCreate finished")
}
@ -170,21 +193,28 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
clientUid: Int,
rootHints: Bundle?
): BrowserRoot {
Timber.d("AutoMediaBrowserService onGetRoot called. clientPackageName: %s; clientUid: %d", clientPackageName, clientUid)
Timber.d(
"AutoMediaBrowserService onGetRoot called. clientPackageName: %s; clientUid: %d",
clientPackageName, clientUid
)
val extras = Bundle()
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
)
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
)
extras.putBoolean(
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
)
return BrowserRoot(MEDIA_ROOT_ID, extras)
}
@Suppress("ReturnCount", "ComplexMethod")
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
@ -199,7 +229,9 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
MEDIA_ARTIST_ID -> return getArtists(result)
MEDIA_ARTIST_SECTION -> return getArtists(result, parentIdParts[1])
MEDIA_ALBUM_ID -> return getAlbums(result, AlbumListType.SORTED_BY_NAME)
MEDIA_ALBUM_PAGE_ID -> return getAlbums(result, AlbumListType.fromName(parentIdParts[1]), parentIdParts[2].toInt())
MEDIA_ALBUM_PAGE_ID -> return getAlbums(
result, AlbumListType.fromName(parentIdParts[1]), parentIdParts[2].toInt()
)
MEDIA_PLAYLIST_ID -> return getPlaylists(result)
MEDIA_ALBUM_FREQUENT_ID -> return getAlbums(result, AlbumListType.FREQUENT)
MEDIA_ALBUM_NEWEST_ID -> return getAlbums(result, AlbumListType.NEWEST)
@ -212,7 +244,9 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
MEDIA_BOOKMARK_ID -> return getBookmarks(result)
MEDIA_PODCAST_ID -> return getPodcasts(result)
MEDIA_PLAYLIST_ITEM -> return getPlaylist(parentIdParts[1], parentIdParts[2], result)
MEDIA_ARTIST_ITEM -> return getAlbumsForArtist(result, parentIdParts[1], parentIdParts[2])
MEDIA_ARTIST_ITEM -> return getAlbumsForArtist(
result, parentIdParts[1], parentIdParts[2]
)
MEDIA_ALBUM_ITEM -> return getSongsForAlbum(result, parentIdParts[1], parentIdParts[2])
MEDIA_SHARE_ITEM -> return getSongsForShare(result, parentIdParts[1])
MEDIA_PODCAST_ITEM -> return getPodcastEpisodes(result, parentIdParts[1])
@ -230,7 +264,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
result.detach()
serviceScope.launch {
val criteria = SearchCriteria(query, searchLimit, searchLimit, searchLimit)
val criteria = SearchCriteria(query, SEARCH_LIMIT, SEARCH_LIMIT, SEARCH_LIMIT)
val searchResult = callWithErrorHandling { musicService.search(criteria) }
// TODO Add More... button to categories
@ -272,7 +306,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
}
private fun playSearch(id : String) {
private fun playSearch(id: String) {
serviceScope.launch {
// If there is no cache, we can't play the selected song.
if (searchSongsCache != null) {
@ -380,7 +414,10 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
result.sendResult(mediaItems)
}
private fun getArtists(result: Result<MutableList<MediaBrowserCompat.MediaItem>>, section: String? = null) {
private fun getArtists(
result: Result<MutableList<MediaBrowserCompat.MediaItem>>,
section: String? = null
) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
@ -404,7 +441,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
// If there are too many artists, create alphabetic index of them
if (section == null && artists.count() > displayLimit) {
if (section == null && artists.count() > DISPLAY_LIMIT) {
val index = mutableListOf<String>()
// TODO This sort should use ignoredArticles somehow...
artists = artists.sortedBy { artist -> artist.name }
@ -442,7 +479,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
result.detach()
serviceScope.launch {
val albums = if (!isOffline && useId3Tags) {
callWithErrorHandling { musicService.getArtist(id, name,false) }
callWithErrorHandling { musicService.getArtist(id, name, false) }
} else {
callWithErrorHandling { musicService.getMusicDirectory(id, name, false) }
}
@ -477,7 +514,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
mediaItems.addPlayAllItem(listOf(MEDIA_ALBUM_ITEM, id, name).joinToString("|"))
// TODO: Paging is not implemented for songs, is it necessary at all?
val items = songs.getChildren().take(displayLimit)
val items = songs.getChildren().take(DISPLAY_LIMIT)
items.map { item ->
if (item.isDirectory)
mediaItems.add(
@ -518,11 +555,19 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
serviceScope.launch {
val offset = (page ?: 0) * displayLimit
val offset = (page ?: 0) * DISPLAY_LIMIT
val albums = if (useId3Tags) {
callWithErrorHandling { musicService.getAlbumList2(type.typeName, displayLimit, offset, null) }
callWithErrorHandling {
musicService.getAlbumList2(
type.typeName, DISPLAY_LIMIT, offset, null
)
}
} else {
callWithErrorHandling { musicService.getAlbumList(type.typeName, displayLimit, offset, null) }
callWithErrorHandling {
musicService.getAlbumList(
type.typeName, DISPLAY_LIMIT, offset, null
)
}
}
albums?.getAllChild()?.map { album ->
@ -534,7 +579,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
)
}
if (albums?.getAllChild()?.count() ?: 0 >= displayLimit)
if (albums?.getAllChild()?.count() ?: 0 >= DISPLAY_LIMIT)
mediaItems.add(
R.string.search_more,
listOf(MEDIA_ALBUM_PAGE_ID, type.typeName, (page ?: 0) + 1).joinToString("|"),
@ -564,7 +609,11 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
}
private fun getPlaylist(id: String, name: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
private fun getPlaylist(
id: String,
name: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
@ -579,7 +628,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
// Playlist should be cached as it may contain random elements
playlistCache = content.getAllChild()
playlistCache!!.take(displayLimit).map { item ->
playlistCache!!.take(DISPLAY_LIMIT).map { item ->
mediaItems.add(
MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
@ -618,7 +667,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
val content = callWithErrorHandling { musicService.getPlaylist(id, name) }
playlistCache = content?.getAllChild()
}
val song = playlistCache?.firstOrNull{x -> x.id == songId}
val song = playlistCache?.firstOrNull { x -> x.id == songId }
if (song != null) playSong(song)
}
}
@ -633,7 +682,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
private fun playAlbumSong(id: String, name: String, songId: String) {
serviceScope.launch {
val songs = listSongsInMusicService(id, name)
val song = songs?.getAllChild()?.firstOrNull{x -> x.id == songId}
val song = songs?.getAllChild()?.firstOrNull { x -> x.id == songId }
if (song != null) playSong(song)
}
}
@ -669,14 +718,16 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
mediaItems.addPlayAllItem(listOf(MEDIA_PODCAST_ITEM, id).joinToString("|"))
episodes.getAllChild().map { episode ->
mediaItems.add(MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
episode,
listOf(MEDIA_PODCAST_EPISODE_ITEM, id, episode.id)
.joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
))
mediaItems.add(
MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
episode,
listOf(MEDIA_PODCAST_EPISODE_ITEM, id, episode.id)
.joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
)
}
result.sendResult(mediaItems)
}
@ -713,13 +764,15 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
val songs = Util.getSongsFromBookmarks(bookmarks)
songs.getAllChild().map { song ->
mediaItems.add(MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
song,
listOf(MEDIA_BOOKMARK_ITEM, song.id).joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
))
mediaItems.add(
MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
song,
listOf(MEDIA_BOOKMARK_ITEM, song.id).joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
)
}
result.sendResult(mediaItems)
}
@ -731,7 +784,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
val bookmarks = callWithErrorHandling { musicService.getBookmarks() }
if (bookmarks != null) {
val songs = Util.getSongsFromBookmarks(bookmarks)
val song = songs.getAllChild().firstOrNull{song -> song.id == id}
val song = songs.getAllChild().firstOrNull { song -> song.id == id }
if (song != null) playSong(song)
}
}
@ -766,20 +819,22 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
serviceScope.launch {
val shares = callWithErrorHandling { musicService.getShares(false) }
val selectedShare = shares?.firstOrNull{share -> share.id == id }
val selectedShare = shares?.firstOrNull { share -> share.id == id }
if (selectedShare != null) {
if (selectedShare.getEntries().count() > 1)
mediaItems.addPlayAllItem(listOf(MEDIA_SHARE_ITEM, id).joinToString("|"))
selectedShare.getEntries().map { song ->
mediaItems.add(MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
song,
listOf(MEDIA_SHARE_SONG_ITEM, id, song.id).joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
))
mediaItems.add(
MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
song,
listOf(MEDIA_SHARE_SONG_ITEM, id, song.id).joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
)
}
}
result.sendResult(mediaItems)
@ -789,7 +844,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
private fun playShare(id: String) {
serviceScope.launch {
val shares = callWithErrorHandling { musicService.getShares(false) }
val selectedShare = shares?.firstOrNull{share -> share.id == id }
val selectedShare = shares?.firstOrNull { share -> share.id == id }
if (selectedShare != null) {
playSongs(selectedShare.getEntries())
}
@ -799,9 +854,9 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
private fun playShareSong(id: String, songId: String) {
serviceScope.launch {
val shares = callWithErrorHandling { musicService.getShares(false) }
val selectedShare = shares?.firstOrNull{share -> share.id == id }
val selectedShare = shares?.firstOrNull { share -> share.id == id }
if (selectedShare != null) {
val song = selectedShare.getEntries().firstOrNull{x -> x.id == songId}
val song = selectedShare.getEntries().firstOrNull { x -> x.id == songId }
if (song != null) playSong(song)
}
}
@ -819,7 +874,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
mediaItems.addPlayAllItem(listOf(MEDIA_SONG_STARRED_ID).joinToString("|"))
// TODO: Paging is not implemented for songs, is it necessary at all?
val items = songs.songs.take(displayLimit)
val items = songs.songs.take(DISPLAY_LIMIT)
starredSongsCache = items
items.map { song ->
mediaItems.add(
@ -855,7 +910,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
val content = listStarredSongsInMusicService()
starredSongsCache = content?.songs
}
val song = starredSongsCache?.firstOrNull{x -> x.id == songId}
val song = starredSongsCache?.firstOrNull { x -> x.id == songId }
if (song != null) playSong(song)
}
}
@ -865,7 +920,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
result.detach()
serviceScope.launch {
val songs = callWithErrorHandling { musicService.getRandomSongs(displayLimit) }
val songs = callWithErrorHandling { musicService.getRandomSongs(DISPLAY_LIMIT) }
if (songs != null) {
if (songs.getAllChild().count() > 1)
@ -895,7 +950,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
if (randomSongsCache == null) {
// This can only happen if Android Auto cached items, but Ultrasonic has forgot them
// In this case we request a new set of random songs
val content = callWithErrorHandling { musicService.getRandomSongs(displayLimit) }
val content = callWithErrorHandling { musicService.getRandomSongs(DISPLAY_LIMIT) }
randomSongsCache = content?.getAllChild()
}
if (randomSongsCache != null) playSongs(randomSongsCache)
@ -942,10 +997,14 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
builder.setIconUri(Util.getUriToDrawable(applicationContext, icon))
if (groupNameId != null)
builder.setExtras(Bundle().apply { putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
getString(groupNameId)
) })
builder.setExtras(
Bundle().apply {
putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
getString(groupNameId)
)
}
)
val mediaItem = MediaBrowserCompat.MediaItem(
builder.build(),
@ -970,10 +1029,14 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
builder.setIconUri(Util.getUriToDrawable(applicationContext, icon))
if (groupNameId != null)
builder.setExtras(Bundle().apply { putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
getString(groupNameId)
) })
builder.setExtras(
Bundle().apply {
putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
getString(groupNameId)
)
}
)
val mediaItem = MediaBrowserCompat.MediaItem(
builder.build(),
@ -1034,4 +1097,4 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
null
}
}
}
}

View File

@ -1,6 +1,16 @@
/*
* DownloadQueueSerializer.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.service
import android.content.Context
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -11,9 +21,6 @@ import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.MediaSessionHandler
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
/**
* This class is responsible for the serialization / deserialization
@ -102,4 +109,4 @@ class DownloadQueueSerializer : KoinComponent {
mediaSessionHandler.updateMediaSessionQueue(state.songs)
afterDeserialized.accept(state)
}
}
}

View File

@ -21,13 +21,13 @@ import android.os.PowerManager
import android.os.PowerManager.PARTIAL_WAKE_LOCK
import android.os.PowerManager.WakeLock
import androidx.lifecycle.MutableLiveData
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
import java.net.URLEncoder
import java.util.Locale
import kotlin.math.abs
import kotlin.math.max
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
@ -42,7 +42,8 @@ import timber.log.Timber
/**
* Represents a Media Player which uses the mobile's resources for playback
*/
class LocalMediaPlayer: KoinComponent {
@Suppress("TooManyFunctions")
class LocalMediaPlayer : KoinComponent {
private val audioFocusHandler by inject<AudioFocusHandler>()
private val context by inject<Context>()

View File

@ -1,21 +1,10 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
* MediaPlayerLifecycleSupport.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.service
import android.content.BroadcastReceiver
@ -25,7 +14,6 @@ import android.content.IntentFilter
import android.media.AudioManager
import android.os.Build
import android.view.KeyEvent
import kotlinx.coroutines.newFixedThreadPoolContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.R
@ -85,7 +73,8 @@ class MediaPlayerLifecycleSupport : KoinComponent {
false
)
// Work-around: Serialize again, as the restore() method creates a serialization without current playing info.
// Work-around: Serialize again, as the restore() method creates a
// serialization without current playing info.
downloadQueueSerializer.serializeDownloadQueue(
downloader.downloadList,
downloader.currentPlayingIndex,
@ -179,14 +168,15 @@ class MediaPlayerLifecycleSupport : KoinComponent {
val headsetIntentFilter: IntentFilter =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
IntentFilter(AudioManager.ACTION_HEADSET_PLUG)
} else {
IntentFilter(Intent.ACTION_HEADSET_PLUG)
}
IntentFilter(AudioManager.ACTION_HEADSET_PLUG)
} else {
IntentFilter(Intent.ACTION_HEADSET_PLUG)
}
applicationContext().registerReceiver(headsetEventReceiver, headsetIntentFilter)
}
@Suppress("MagicNumber", "ComplexMethod")
private fun handleKeyEvent(event: KeyEvent) {
if (event.action != KeyEvent.ACTION_DOWN || event.repeatCount > 0) return
@ -195,9 +185,10 @@ class MediaPlayerLifecycleSupport : KoinComponent {
val receivedKeyCode = event.keyCode
// Translate PLAY and PAUSE codes to PLAY_PAUSE to improve compatibility with old Bluetooth devices
keyCode = if (Util.getSingleButtonPlayPause() &&
(receivedKeyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
receivedKeyCode == KeyEvent.KEYCODE_MEDIA_PAUSE)
keyCode = if (Util.getSingleButtonPlayPause() && (
receivedKeyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
receivedKeyCode == KeyEvent.KEYCODE_MEDIA_PAUSE
)
) {
Timber.i("Single button Play/Pause is set, rewriting keyCode to PLAY_PAUSE")
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
@ -221,10 +212,10 @@ class MediaPlayerLifecycleSupport : KoinComponent {
KeyEvent.KEYCODE_MEDIA_PLAY ->
if (mediaPlayerController.playerState === PlayerState.IDLE) {
mediaPlayerController.play()
} else if (mediaPlayerController.playerState !== PlayerState.STARTED) {
mediaPlayerController.start()
}
mediaPlayerController.play()
} else if (mediaPlayerController.playerState !== PlayerState.STARTED) {
mediaPlayerController.start()
}
KeyEvent.KEYCODE_MEDIA_PAUSE -> mediaPlayerController.pause()
KeyEvent.KEYCODE_1 -> mediaPlayerController.setSongRating(1)
@ -242,13 +233,18 @@ class MediaPlayerLifecycleSupport : KoinComponent {
/**
* This function processes the intent that could come from other applications.
*/
@Suppress("ComplexMethod")
private fun handleUltrasonicIntent(intentAction: String) {
val isRunning = created
// If Ultrasonic is not running, do nothing to stop or pause
if (!isRunning && (intentAction == Constants.CMD_PAUSE ||
intentAction == Constants.CMD_STOP)) return
if (
!isRunning && (
intentAction == Constants.CMD_PAUSE ||
intentAction == Constants.CMD_STOP
)
) return
val autoStart =
intentAction == Constants.CMD_PLAY ||
@ -261,7 +257,9 @@ class MediaPlayerLifecycleSupport : KoinComponent {
onCreate(autoStart) {
when (intentAction) {
Constants.CMD_PLAY -> mediaPlayerController.play()
Constants.CMD_RESUME_OR_PLAY -> // If Ultrasonic wasn't running, the autoStart is enough to resume, no need to call anything
Constants.CMD_RESUME_OR_PLAY ->
// If Ultrasonic wasn't running, the autoStart is enough to resume,
// no need to call anything
if (isRunning) mediaPlayerController.resumeOrPlay()
Constants.CMD_NEXT -> mediaPlayerController.next()
@ -277,4 +275,4 @@ class MediaPlayerLifecycleSupport : KoinComponent {
}
}
}
}
}

View File

@ -7,7 +7,11 @@
package org.moire.ultrasonic.service
import android.app.*
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
@ -93,7 +97,7 @@ class MediaPlayerService : Service() {
localMediaPlayer.onNextSongRequested = Runnable { setNextPlaying() }
mediaSessionEventListener = object:MediaSessionEventListener {
mediaSessionEventListener = object : MediaSessionEventListener {
override fun onMediaSessionTokenCreated(token: MediaSessionCompat.Token) {
mediaSessionToken = token
}
@ -383,7 +387,11 @@ class MediaPlayerService : Service() {
val context = this@MediaPlayerService
// Notify MediaSession
mediaSessionHandler.updateMediaSession(currentPlaying, downloader.currentPlayingIndex.toLong(), playerState)
mediaSessionHandler.updateMediaSession(
currentPlaying,
downloader.currentPlayingIndex.toLong(),
playerState
)
if (playerState === PlayerState.PAUSED) {
downloadQueueSerializer.serializeDownloadQueue(
@ -535,7 +543,11 @@ class MediaPlayerService : Service() {
// Init
val context = applicationContext
val song = currentPlaying?.song
val stopIntent = Util.getPendingIntentForMediaAction(context, KeyEvent.KEYCODE_MEDIA_STOP, 100)
val stopIntent = Util.getPendingIntentForMediaAction(
context,
KeyEvent.KEYCODE_MEDIA_STOP,
100
)
// We should use a single notification builder, otherwise the notification may not be updated
if (notificationBuilder == null) {

View File

@ -1,3 +1,10 @@
/*
* MediaSessionEventDistributor.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.os.Bundle
@ -41,7 +48,10 @@ class MediaSessionEventDistributor {
}
fun raisePlayFromMediaIdRequestedEvent(mediaId: String?, extras: Bundle?) {
eventListenerList.forEach { listener -> listener.onPlayFromMediaIdRequested(mediaId, extras) }
eventListenerList.forEach {
listener ->
listener.onPlayFromMediaIdRequested(mediaId, extras)
}
}
fun raisePlayFromSearchRequestedEvent(query: String?, extras: Bundle?) {

View File

@ -1,3 +1,10 @@
/*
* MediaSessionEventListener.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.os.Bundle

View File

@ -1,3 +1,10 @@
/*
* MediaSessionHandler.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.app.PendingIntent
@ -21,7 +28,7 @@ import org.moire.ultrasonic.service.DownloadFile
import timber.log.Timber
private const val INTENT_CODE_MEDIA_BUTTON = 161
private const val CALL_DIVIDE = 10
/**
* Central place to handle the state of the MediaSession
*/
@ -157,7 +164,12 @@ class MediaSessionHandler : KoinComponent {
Timber.i("MediaSessionHandler.initialize Media Session created")
}
fun updateMediaSession(currentPlaying: DownloadFile?, currentPlayingIndex: Long?, playerState: PlayerState) {
@Suppress("TooGenericExceptionCaught", "LongMethod")
fun updateMediaSession(
currentPlaying: DownloadFile?,
currentPlayingIndex: Long?,
playerState: PlayerState
) {
Timber.d("Updating the MediaSession")
// Set Metadata
@ -240,18 +252,20 @@ class MediaSessionHandler : KoinComponent {
mediaSession!!.setPlaybackState(playbackStateBuilder.build())
}
fun updateMediaSessionQueue(playlist: Iterable<MusicDirectory.Entry>)
{
fun updateMediaSessionQueue(playlist: Iterable<MusicDirectory.Entry>) {
// This call is cached because Downloader may initialize earlier than the MediaSession
cachedPlaylist = playlist
if (mediaSession == null) return
mediaSession!!.setQueueTitle(applicationContext.getString(R.string.button_bar_now_playing))
mediaSession!!.setQueue(playlist.mapIndexed { id, song ->
MediaSessionCompat.QueueItem(
Util.getMediaDescriptionForEntry(song),
id.toLong())
})
mediaSession!!.setQueue(
playlist.mapIndexed { id, song ->
MediaSessionCompat.QueueItem(
Util.getMediaDescriptionForEntry(song),
id.toLong()
)
}
)
}
fun updateMediaSessionPlaybackPosition(playbackPosition: Long) {
@ -264,7 +278,7 @@ class MediaSessionHandler : KoinComponent {
// Playback position is updated too frequently in the player.
// This counter makes sure that the MediaSession is updated ~ at every second
playbackPositionDelayCount++
if (playbackPositionDelayCount < 10) return
if (playbackPositionDelayCount < CALL_DIVIDE) return
playbackPositionDelayCount = 0
val playbackStateBuilder = PlaybackStateCompat.Builder()
@ -286,7 +300,10 @@ class MediaSessionHandler : KoinComponent {
}
private fun registerMediaButtonEventReceiver() {
val component = ComponentName(applicationContext.packageName, MediaButtonIntentReceiver::class.java.name)
val component = ComponentName(
applicationContext.packageName,
MediaButtonIntentReceiver::class.java.name
)
val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON)
mediaButtonIntent.component = component
@ -303,4 +320,4 @@ class MediaSessionHandler : KoinComponent {
private fun unregisterMediaButtonEventReceiver() {
mediaSession?.setMediaButtonReceiver(null)
}
}
}

View File

@ -1,21 +1,10 @@
/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
* Util.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.util
import android.annotation.SuppressLint
@ -51,16 +40,6 @@ import android.widget.Toast
import androidx.annotation.AnyRes
import androidx.media.utils.MediaConstants
import androidx.preference.PreferenceManager
import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp.Companion.applicationContext
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.service.DownloadFile
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.File
@ -72,17 +51,32 @@ import java.io.OutputStream
import java.io.UnsupportedEncodingException
import java.security.MessageDigest
import java.text.DecimalFormat
import java.util.*
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp.Companion.applicationContext
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.service.DownloadFile
import timber.log.Timber
private const val LINE_LENGTH = 60
private const val DEGRADE_PRECISION_AFTER = 10
private const val MINUTES_IN_HOUR = 60
private const val KBYTE = 1024
/**
* @author Sindre Mehus
* @version $Id$
* Contains various utility functions
*/
@Suppress("TooManyFunctions", "LargeClass")
object Util {
private val GIGA_BYTE_FORMAT = DecimalFormat("0.00 GB")
@ -171,17 +165,17 @@ object Util {
fun applyTheme(context: Context?) {
val theme = getTheme()
if (Constants.PREFERENCES_KEY_THEME_DARK.equals(
theme,
ignoreCase = true
) || "fullscreen".equals(theme, ignoreCase = true)
theme,
ignoreCase = true
) || "fullscreen".equals(theme, ignoreCase = true)
) {
context!!.setTheme(R.style.UltrasonicTheme)
} else if (Constants.PREFERENCES_KEY_THEME_BLACK.equals(theme, ignoreCase = true)) {
context!!.setTheme(R.style.UltrasonicTheme_Black)
} else if (Constants.PREFERENCES_KEY_THEME_LIGHT.equals(
theme,
ignoreCase = true
) || "fullscreenlight".equals(theme, ignoreCase = true)
theme,
ignoreCase = true
) || "fullscreenlight".equals(theme, ignoreCase = true)
) {
context!!.setTheme(R.style.UltrasonicTheme_Light)
}
@ -248,8 +242,9 @@ object Util {
}
@Throws(IOException::class)
@Suppress("MagicNumber")
fun copy(input: InputStream, output: OutputStream): Long {
val buffer = ByteArray(1024 * 4)
val buffer = ByteArray(KBYTE * 4)
var count: Long = 0
var n: Int
while (-1 != input.read(buffer).also { n = it }) {
@ -261,14 +256,16 @@ object Util {
@Throws(IOException::class)
fun atomicCopy(from: File, to: File) {
val tmp = File(String.format("%s.tmp", to.path))
val `in` = FileInputStream(from)
val tmp = File(String.format(Locale.ROOT, "%s.tmp", to.path))
val input = FileInputStream(from)
val out = FileOutputStream(tmp)
try {
`in`.channel.transferTo(0, from.length(), out.channel)
input.channel.transferTo(0, from.length(), out.channel)
out.close()
if (!tmp.renameTo(to)) {
throw IOException(String.format("Failed to rename %s to %s", tmp, to))
throw IOException(
String.format(Locale.ROOT, "Failed to rename %s to %s", tmp, to)
)
}
Timber.i("Copied %s to %s", from, to)
} catch (x: IOException) {
@ -276,7 +273,7 @@ object Util {
delete(to)
throw x
} finally {
close(`in`)
close(input)
close(out)
delete(tmp)
}
@ -296,7 +293,7 @@ object Util {
fun close(closeable: Closeable?) {
try {
closeable?.close()
} catch (x: Throwable) {
} catch (_: Throwable) {
// Ignored
}
}
@ -376,18 +373,18 @@ object Util {
fun formatBytes(byteCount: Long): String {
// More than 1 GB?
if (byteCount >= 1024 * 1024 * 1024) {
return GIGA_BYTE_FORMAT.format(byteCount.toDouble() / (1024 * 1024 * 1024))
if (byteCount >= KBYTE * KBYTE * KBYTE) {
return GIGA_BYTE_FORMAT.format(byteCount.toDouble() / (KBYTE * KBYTE * KBYTE))
}
// More than 1 MB?
if (byteCount >= 1024 * 1024) {
return MEGA_BYTE_FORMAT.format(byteCount.toDouble() / (1024 * 1024))
if (byteCount >= KBYTE * KBYTE) {
return MEGA_BYTE_FORMAT.format(byteCount.toDouble() / (KBYTE * KBYTE))
}
// More than 1 KB?
return if (byteCount >= 1024) {
KILO_BYTE_FORMAT.format(byteCount.toDouble() / 1024)
return if (byteCount >= KBYTE) {
KILO_BYTE_FORMAT.format(byteCount.toDouble() / KBYTE)
} else "$byteCount B"
}
@ -406,35 +403,36 @@ object Util {
* @return The formatted string.
*/
@Synchronized
@Suppress("ReturnCount")
fun formatLocalizedBytes(byteCount: Long, context: Context): String {
// More than 1 GB?
if (byteCount >= 1024 * 1024 * 1024) {
if (byteCount >= KBYTE * KBYTE * KBYTE) {
if (GIGA_BYTE_LOCALIZED_FORMAT == null) {
GIGA_BYTE_LOCALIZED_FORMAT =
DecimalFormat(context.resources.getString(R.string.util_bytes_format_gigabyte))
}
return GIGA_BYTE_LOCALIZED_FORMAT!!
.format(byteCount.toDouble() / (1024 * 1024 * 1024))
.format(byteCount.toDouble() / (KBYTE * KBYTE * KBYTE))
}
// More than 1 MB?
if (byteCount >= 1024 * 1024) {
if (byteCount >= KBYTE * KBYTE) {
if (MEGA_BYTE_LOCALIZED_FORMAT == null) {
MEGA_BYTE_LOCALIZED_FORMAT =
DecimalFormat(context.resources.getString(R.string.util_bytes_format_megabyte))
}
return MEGA_BYTE_LOCALIZED_FORMAT!!
.format(byteCount.toDouble() / (1024 * 1024))
.format(byteCount.toDouble() / (KBYTE * KBYTE))
}
// More than 1 KB?
if (byteCount >= 1024) {
if (byteCount >= KBYTE) {
if (KILO_BYTE_LOCALIZED_FORMAT == null) {
KILO_BYTE_LOCALIZED_FORMAT =
DecimalFormat(context.resources.getString(R.string.util_bytes_format_kilobyte))
}
return KILO_BYTE_LOCALIZED_FORMAT!!.format(byteCount.toDouble() / 1024)
return KILO_BYTE_LOCALIZED_FORMAT!!.format(byteCount.toDouble() / KBYTE)
}
if (BYTE_LOCALIZED_FORMAT == null) {
BYTE_LOCALIZED_FORMAT =
@ -453,6 +451,7 @@ object Util {
* @param s The string to encode.
* @return The encoded string.
*/
@Suppress("TooGenericExceptionThrown", "TooGenericExceptionCaught")
fun utf8HexEncode(s: String?): String? {
if (s == null) {
return null
@ -473,6 +472,7 @@ object Util {
* @param data Bytes to convert to hexadecimal characters.
* @return A string containing hexadecimal characters.
*/
@Suppress("MagicNumber")
fun hexEncode(data: ByteArray): String {
val length = data.size
val out = CharArray(length shl 1)
@ -493,6 +493,7 @@ object Util {
* @return MD5 digest as a hex string.
*/
@JvmStatic
@Suppress("TooGenericExceptionThrown", "TooGenericExceptionCaught")
fun md5Hex(s: String?): String? {
return if (s == null) {
null
@ -567,7 +568,11 @@ object Util {
.setIcon(icon)
.setTitle(titleId)
.setMessage(message)
.setPositiveButton(R.string.common_ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
.setPositiveButton(R.string.common_ok) {
dialog: DialogInterface,
_: Int ->
dialog.dismiss()
}
.show()
}
@ -746,6 +751,7 @@ object Util {
context.sendBroadcast(avrcpIntent)
}
@Suppress("LongParameterList")
fun broadcastA2dpPlayStatusChange(
context: Context,
state: PlayerState?,
@ -763,7 +769,7 @@ object Util {
return
}
// FIXME: This is probably a bug.
// FIXME This is probably a bug.
if (currentSong !== currentSong) {
Util.currentSong = currentSong
}
@ -797,11 +803,12 @@ object Util {
when (state) {
PlayerState.STARTED -> avrcpIntent.putExtra("playing", true)
PlayerState.STOPPED, PlayerState.PAUSED, PlayerState.COMPLETED -> avrcpIntent.putExtra(
PlayerState.STOPPED, PlayerState.PAUSED,
PlayerState.COMPLETED -> avrcpIntent.putExtra(
"playing",
false
)
else -> return // No need to broadcast.
else -> return // No need to broadcast.
}
context.sendBroadcast(avrcpIntent)
@ -819,12 +826,13 @@ object Util {
PlayerState.STOPPED -> intent.putExtra("state", "stop")
PlayerState.PAUSED -> intent.putExtra("state", "pause")
PlayerState.COMPLETED -> intent.putExtra("state", "complete")
else -> return // No need to broadcast.
else -> return // No need to broadcast.
}
context.sendBroadcast(intent)
}
@JvmStatic
@Suppress("MagicNumber")
fun getNotificationImageSize(context: Context): Int {
val metrics = context.resources.displayMetrics
val imageSizeLarge =
@ -838,6 +846,7 @@ object Util {
}
}
@Suppress("MagicNumber")
fun getAlbumImageSize(context: Context?): Int {
val metrics = context!!.resources.displayMetrics
val imageSizeLarge =
@ -1027,11 +1036,11 @@ object Util {
}
val hours = TimeUnit.MILLISECONDS.toHours(millis)
val minutes = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(hours)
val seconds =
TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(hours * 60 + minutes)
val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) -
TimeUnit.MINUTES.toSeconds(hours * MINUTES_IN_HOUR + minutes)
return when {
hours >= 10 -> {
hours >= DEGRADE_PRECISION_AFTER -> {
String.format(
Locale.getDefault(),
"%02d:%02d:%02d",
@ -1043,7 +1052,7 @@ object Util {
hours > 0 -> {
String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, seconds)
}
minutes >= 10 -> {
minutes >= DEGRADE_PRECISION_AFTER -> {
String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds)
}
minutes > 0 -> String.format(
@ -1254,12 +1263,13 @@ object Util {
fun getUriToDrawable(context: Context, @AnyRes drawableId: Int): Uri {
return Uri.parse(
ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + context.resources.getResourcePackageName(drawableId)
+ '/' + context.resources.getResourceTypeName(drawableId)
+ '/' + context.resources.getResourceEntryName(drawableId)
"://" + context.resources.getResourcePackageName(drawableId) +
'/' + context.resources.getResourceTypeName(drawableId) +
'/' + context.resources.getResourceEntryName(drawableId)
)
}
@Suppress("ComplexMethod")
fun getMediaDescriptionForEntry(
song: MusicDirectory.Entry,
mediaId: String? = null,
@ -1267,12 +1277,14 @@ object Util {
): MediaDescriptionCompat {
val descriptionBuilder = MediaDescriptionCompat.Builder()
val artist = StringBuilder(60)
val artist = StringBuilder(LINE_LENGTH)
var bitRate: String? = null
val duration = song.duration
if (duration != null) {
artist.append(String.format("%s ", formatTotalDuration(duration.toLong())))
artist.append(
String.format(Locale.ROOT, "%s ", formatTotalDuration(duration.toLong()))
)
}
if (song.bitRate != null && song.bitRate!! > 0)
@ -1286,16 +1298,21 @@ object Util {
fileFormat = if (
TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo
) suffix else String.format("%s > %s", suffix, transcodedSuffix)
) suffix else String.format(Locale.ROOT, "%s > %s", suffix, transcodedSuffix)
val artistName = song.artist
if (artistName != null) {
if (shouldDisplayBitrateWithArtist() && (!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank())) {
if (shouldDisplayBitrateWithArtist() && (
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
)
) {
artist.append(artistName).append(" (").append(
String.format(
appContext().getString(R.string.song_details_all),
if (bitRate == null) "" else String.format("%s ", bitRate), fileFormat
if (bitRate == null) ""
else String.format(Locale.ROOT, "%s ", bitRate),
fileFormat
)
).append(')')
} else {
@ -1305,9 +1322,9 @@ object Util {
val trackNumber = song.track ?: 0
val title = StringBuilder(60)
val title = StringBuilder(LINE_LENGTH)
if (shouldShowTrackNumber() && trackNumber > 0)
title.append(String.format("%02d - ", trackNumber))
title.append(String.format(Locale.ROOT, "%02d - ", trackNumber))
title.append(song.title)
@ -1315,16 +1332,22 @@ object Util {
title.append(" (").append(
String.format(
appContext().getString(R.string.song_details_all),
if (bitRate == null) "" else String.format("%s ", bitRate), fileFormat
if (bitRate == null) ""
else String.format(Locale.ROOT, "%s ", bitRate),
fileFormat
)
).append(')')
}
if (groupNameId != null)
descriptionBuilder.setExtras(Bundle().apply { putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
appContext().getString(groupNameId)
) })
descriptionBuilder.setExtras(
Bundle().apply {
putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
appContext().getString(groupNameId)
)
}
)
descriptionBuilder.setTitle(title)
descriptionBuilder.setSubtitle(artist)
@ -1344,4 +1367,4 @@ object Util {
intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keycode))
return PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
}
}

View File

@ -2,9 +2,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:fillColor="#FFF"
android:pathData="M9,13.75c-2.34,0 -7,1.17 -7,3.5L2,19h14v-1.75c0,-2.33 -4.66,-3.5 -7,-3.5zM4.34,17c0.84,-0.58 2.87,-1.25 4.66,-1.25s3.82,0.67 4.66,1.25L4.34,17zM9,12c1.93,0 3.5,-1.57 3.5,-3.5S10.93,5 9,5 5.5,6.57 5.5,8.5 7.07,12 9,12zM9,7c0.83,0 1.5,0.67 1.5,1.5S9.83,10 9,10s-1.5,-0.67 -1.5,-1.5S8.17,7 9,7zM16.04,13.81c1.16,0.84 1.96,1.96 1.96,3.44L18,19h4v-1.75c0,-2.02 -3.5,-3.17 -5.96,-3.44zM15,12c1.93,0 3.5,-1.57 3.5,-3.5S16.93,5 15,5c-0.54,0 -1.04,0.13 -1.5,0.35 0.63,0.89 1,1.98 1,3.15s-0.37,2.26 -1,3.15c0.46,0.22 0.96,0.35 1.5,0.35z"/>
</vector>

View File

@ -2,10 +2,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:fillColor="#FFF"
android:pathData="M22,6h-5v8.18C16.69,14.07 16.35,14 16,14c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3V8h3V6zM15,6H3v2h12V6zM15,10H3v2h12V10zM11,14H3v2h8V14z"/>
</vector>