diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index 08a2d5aa..e80ca5f7 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + package="org.moire.ultrasonic" + android:installLocation="auto"> @@ -60,6 +61,7 @@ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt index 2e9810c4..f50883d6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaBrowserService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaBrowserService.kt index 2cea930a..e834cd42 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaBrowserService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaBrowserService.kt @@ -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> @@ -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>, section: String? = null) { + private fun getArtists( + result: Result>, + section: String? = null + ) { val mediaItems: MutableList = 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() // 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 = 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>) { + private fun getPlaylist( + id: String, + name: String, + result: Result> + ) { val mediaItems: MutableList = 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 } } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadQueueSerializer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadQueueSerializer.kt index b88a47a0..1cb08b32 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadQueueSerializer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadQueueSerializer.kt @@ -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) } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt index 9b5efa05..b63a0eb9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt @@ -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() private val context by inject() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt index 2917d6f4..3b9a1800 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt @@ -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 . - - 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 { } } } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt index da3a03e7..4e6c14d7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt @@ -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) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventDistributor.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventDistributor.kt index 7313ef85..a9ecade8 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventDistributor.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventDistributor.kt @@ -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?) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventListener.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventListener.kt index f67eb16e..e4075248 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventListener.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionEventListener.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionHandler.kt index 0820af47..c1191b71 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/MediaSessionHandler.kt @@ -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) - { + fun updateMediaSessionQueue(playlist: Iterable) { // 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) } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt index e0b35e66..b0978bb1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt @@ -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 . - - 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) } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/res/drawable/ic_artist.xml b/ultrasonic/src/main/res/drawable/ic_artist.xml index 24b174c7..c3daf609 100644 --- a/ultrasonic/src/main/res/drawable/ic_artist.xml +++ b/ultrasonic/src/main/res/drawable/ic_artist.xml @@ -2,9 +2,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:viewportHeight="24"> diff --git a/ultrasonic/src/main/res/drawable/ic_library.xml b/ultrasonic/src/main/res/drawable/ic_library.xml index ef18b12d..6981f924 100644 --- a/ultrasonic/src/main/res/drawable/ic_library.xml +++ b/ultrasonic/src/main/res/drawable/ic_library.xml @@ -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">