diff --git a/detekt-config.yml b/detekt-config.yml index d095b0b5..47707e69 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -30,6 +30,8 @@ performance: exceptions: active: true + TooGenericExceptionCaught: + allowedExceptionNameRegex: '_|(all|ignore|expected).*' empty-blocks: active: true diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java index 4b2e33cc..619484e2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java @@ -15,10 +15,10 @@ import android.widget.RemoteViews; import org.moire.ultrasonic.R; import org.moire.ultrasonic.activity.NavigationActivity; import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.imageloader.BitmapUtils; import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver; import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.util.Constants; -import org.moire.ultrasonic.util.FileUtil; import timber.log.Timber; @@ -160,7 +160,7 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider // Set the cover art try { - Bitmap bitmap = currentSong == null ? null : FileUtil.getAlbumArtBitmapFromDisk(currentSong, 240, true); + Bitmap bitmap = currentSong == null ? null : BitmapUtils.Companion.getAlbumArtBitmapFromDisk(currentSong, 240); if (bitmap == null) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java index 056f0df2..28b80a0c 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java @@ -19,8 +19,6 @@ package org.moire.ultrasonic.util; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.os.Build; import android.os.Environment; import android.text.TextUtils; @@ -38,6 +36,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Pattern; @@ -127,6 +126,24 @@ public class FileUtil return getAlbumArtFile(albumDir); } + /** + * Get the cache key for a given album entry + * @param entry The album entry + * @return String The hash key + */ + public static String getAlbumArtKey(MusicDirectory.Entry entry) + { + File albumDir = getAlbumDirectory(entry); + File albumArtDir = getAlbumArtDirectory(); + + if (albumArtDir == null || albumDir == null) { + return null; + } + + return String.format(Locale.ROOT, "%s.jpeg", Util.md5Hex(albumDir.getPath())); + } + + public static File getAvatarFile(String username) { File albumArtDir = getAlbumArtDirectory(); @@ -158,119 +175,24 @@ public class FileUtil return new File(albumArtDir, String.format("%s.jpeg", md5Hex)); } - public static Bitmap getAvatarBitmapFromDisk(String username, int size, boolean highQuality) + + /** + * Get the album art file for a given cache key + * @param cacheKey + * @return File object. Not guaranteed that it exists + */ + public static File getAlbumArtFile(String cacheKey) { - if (username == null) return null; + File albumArtDir = getAlbumArtDirectory(); - File avatarFile = getAvatarFile(username); - - Bitmap bitmap = null; - - if (avatarFile != null && avatarFile.exists()) + if (albumArtDir == null || cacheKey == null) { - final BitmapFactory.Options opt = new BitmapFactory.Options(); - - if (size > 0) - { - opt.inJustDecodeBounds = true; - BitmapFactory.decodeFile(avatarFile.getPath(), opt); - - if (highQuality) - { - opt.inDither = true; - opt.inPreferQualityOverSpeed = true; - } - - opt.inPurgeable = true; - opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); - opt.inJustDecodeBounds = false; - } - - try - { - bitmap = BitmapFactory.decodeFile(avatarFile.getPath(), opt); - } - catch (Exception ex) - { - Timber.e(ex, "Exception in BitmapFactory.decodeFile()"); - } - - Timber.i("getAvatarBitmapFromDisk %s", String.valueOf(size)); - - return bitmap; + return null; } - return null; + return new File(albumArtDir, cacheKey); } - public static Bitmap getAlbumArtBitmapFromDisk(MusicDirectory.Entry entry, int size, boolean highQuality) - { - if (entry == null) return null; - - File albumArtFile = getAlbumArtFile(entry); - - Bitmap bitmap = null; - - if (albumArtFile != null && albumArtFile.exists()) - { - final BitmapFactory.Options opt = new BitmapFactory.Options(); - - if (size > 0) - { - opt.inJustDecodeBounds = true; - BitmapFactory.decodeFile(albumArtFile.getPath(), opt); - - if (highQuality) - { - opt.inDither = true; - opt.inPreferQualityOverSpeed = true; - } - - opt.inPurgeable = true; - opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); - opt.inJustDecodeBounds = false; - } - - try - { - bitmap = BitmapFactory.decodeFile(albumArtFile.getPath(), opt); - } - catch (Exception ex) - { - Timber.e(ex, "Exception in BitmapFactory.decodeFile()"); - } - - Timber.i("getAlbumArtBitmapFromDisk %s", String.valueOf(size)); - - return bitmap; - } - - return null; - } - - public static Bitmap getSampledBitmap(byte[] bytes, int size, boolean highQuality) - { - final BitmapFactory.Options opt = new BitmapFactory.Options(); - - if (size > 0) - { - opt.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt); - - if (highQuality) - { - opt.inDither = true; - opt.inPreferQualityOverSpeed = true; - } - - opt.inPurgeable = true; - opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size)); - opt.inJustDecodeBounds = false; - } - - Timber.i("getSampledBitmap %s", String.valueOf(size)); - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt); - } public static File getAlbumArtDirectory() { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java deleted file mode 100644 index e1ae5ae3..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.moire.ultrasonic.util; - -import android.view.View; - -import org.moire.ultrasonic.domain.MusicDirectory; - -public interface ImageLoader { - void loadAvatarImage(View view, String username, boolean large, int size, boolean crossFade, - boolean highQuality); - - void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size, - boolean crossFade, boolean highQuality); - - void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size, - boolean crossFade, boolean highQuality, int defaultResourceId); - -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 9e0e746c..c2c0b6d2 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -12,6 +12,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration import org.moire.ultrasonic.cache.PermanentFileStorage import org.moire.ultrasonic.data.ActiveServerProvider +import org.moire.ultrasonic.imageloader.ImageLoader import org.moire.ultrasonic.log.TimberOkHttpLogger import org.moire.ultrasonic.service.ApiCallResponseChecker import org.moire.ultrasonic.service.CachedMusicService @@ -19,10 +20,10 @@ import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.OfflineMusicService import org.moire.ultrasonic.service.RESTMusicService import org.moire.ultrasonic.subsonic.DownloadHandler +import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker import org.moire.ultrasonic.subsonic.ShareHandler import org.moire.ultrasonic.subsonic.VideoPlayer -import org.moire.ultrasonic.imageloader.SubsonicImageLoader import org.moire.ultrasonic.util.Constants /** @@ -77,6 +78,8 @@ val musicServiceModule = module { OfflineMusicService() } + single { ImageLoader(androidContext(), get(), ImageLoaderProvider.config) } + single { DownloadHandler(get(), get()) } single { NetworkAndStorageChecker(androidContext()) } single { VideoPlayer() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt index 22934478..239fb237 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt @@ -55,8 +55,7 @@ class AlbumRowAdapter( holder.coverArtId = entry.coverArt imageLoader.loadImage( - holder.coverArt, - MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, + holder.coverArt, entry, false, 0, R.drawable.unknown_album ) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt index 3c3e3757..69988607 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt @@ -61,7 +61,10 @@ class ArtistRowAdapter( holder.coverArt.visibility = View.VISIBLE imageLoader.loadImage( holder.coverArt, - MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, + MusicDirectory.Entry("-1").apply { + coverArt = holder.coverArtId + artist = itemList[listPosition].name + }, false, 0, R.drawable.ic_contact_picture ) } else { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt new file mode 100644 index 00000000..fddbec66 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt @@ -0,0 +1,128 @@ +package org.moire.ultrasonic.imageloader + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Build +import org.moire.ultrasonic.domain.MusicDirectory +import org.moire.ultrasonic.util.FileUtil +import org.moire.ultrasonic.util.Util +import timber.log.Timber + +@Suppress("UtilityClassWithPublicConstructor") +class BitmapUtils { + companion object { + fun getAvatarBitmapFromDisk( + username: String?, + size: Int + ): Bitmap? { + if (username == null) return null + val avatarFile = FileUtil.getAvatarFile(username) + val bitmap: Bitmap? = null + if (avatarFile != null && avatarFile.exists()) { + return getBitmapFromDisk(avatarFile.path, size, bitmap) + } + return null + } + + fun getAlbumArtBitmapFromDisk( + entry: MusicDirectory.Entry?, + size: Int + ): Bitmap? { + if (entry == null) return null + val albumArtFile = FileUtil.getAlbumArtFile(entry) + val bitmap: Bitmap? = null + if (albumArtFile != null && albumArtFile.exists()) { + return getBitmapFromDisk(albumArtFile.path, size, bitmap) + } + return null + } + + fun getAlbumArtBitmapFromDisk( + filename: String, + size: Int? + ): Bitmap? { + val albumArtFile = FileUtil.getAlbumArtFile(filename) + val bitmap: Bitmap? = null + if (albumArtFile != null && albumArtFile.exists()) { + return getBitmapFromDisk(albumArtFile.path, size, bitmap) + } + return null + } + + @Suppress("DEPRECATION") + fun getSampledBitmap(bytes: ByteArray, size: Int): Bitmap? { + val opt = BitmapFactory.Options() + if (size > 0) { + // With this flag we only calculate the size first + opt.inJustDecodeBounds = true + + // Decode the size + BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opt) + + // Now set the remaining flags + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + opt.inDither = true + opt.inPreferQualityOverSpeed = true + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + opt.inPurgeable = true + } + + opt.inSampleSize = Util.calculateInSampleSize( + opt, + size, + Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size) + ) + + // Enable real decoding + opt.inJustDecodeBounds = false + } + Timber.i("getSampledBitmap %s", size.toString()) + return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opt) + } + + @Suppress("DEPRECATION") + private fun getBitmapFromDisk( + path: String, + size: Int?, + bitmap: Bitmap? + ): Bitmap? { + var bitmap1 = bitmap + val opt = BitmapFactory.Options() + if (size != null && size > 0) { + // With this flag we only calculate the size first + opt.inJustDecodeBounds = true + + // Decode the size + BitmapFactory.decodeFile(path, opt) + + // Now set the remaining flags + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + opt.inDither = true + opt.inPreferQualityOverSpeed = true + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + opt.inPurgeable = true + } + + opt.inSampleSize = Util.calculateInSampleSize( + opt, + size, + Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size) + ) + + // Enable real decoding + opt.inJustDecodeBounds = false + } + try { + bitmap1 = BitmapFactory.decodeFile(path, opt) + } catch (expected: Exception) { + Timber.e(expected, "Exception in BitmapFactory.decodeFile()") + } + Timber.i("getBitmapFromDisk %s", size.toString()) + return bitmap1 + } + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt index 3c091088..3152e565 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt @@ -1,5 +1,6 @@ package org.moire.ultrasonic.imageloader +import com.squareup.picasso.Picasso.LoadedFrom.DISK import com.squareup.picasso.Picasso.LoadedFrom.NETWORK import com.squareup.picasso.Request import com.squareup.picasso.RequestHandler @@ -23,6 +24,12 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request ?: throw IllegalArgumentException("Nullable id") val size = request.uri.getQueryParameter(SIZE)?.toLong() + // Check if we have a hit in the disk cache + val cache = BitmapUtils.getAlbumArtBitmapFromDisk(request.stableKey!!, size?.toInt()) + if (cache != null) { + return Result(cache, DISK) + } + val response = apiClient.getCoverArt(id, size) if (response.hasError() || response.stream == null) { throw IOException("${response.apiError}") diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt index 63d1c2bb..66b5664f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt @@ -5,11 +5,12 @@ import android.view.View import android.widget.ImageView import com.squareup.picasso.Picasso import com.squareup.picasso.RequestCreator +import java.io.File import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.domain.MusicDirectory -import java.io.File +import org.moire.ultrasonic.util.FileUtil /** * Our new image loader which uses Picasso as a backend. @@ -18,14 +19,13 @@ class ImageLoader( context: Context, apiClient: SubsonicAPIClient, private val config: ImageLoaderConfig -) { +) { private val picasso = Picasso.Builder(context) .addRequestHandler(CoverArtRequestHandler(apiClient)) .addRequestHandler(AvatarRequestHandler(apiClient)) .build().apply { setIndicatorsEnabled(BuildConfig.DEBUG) - Picasso.setSingletonInstance(this) } private fun load(request: ImageRequest) = when (request) { @@ -37,7 +37,7 @@ class ImageLoader( picasso.load(createLoadCoverArtRequest(request.entityId, request.size.toLong())) .addPlaceholder(request) .addError(request) - .stableKey("${request.entityId}-${request.size}") + .stableKey(request.cacheKey) .into(request.imageView) } @@ -80,8 +80,9 @@ class ImageLoader( val requestedSize = resolveSize(size, large) if (id != null && id.isNotEmpty() && view is ImageView) { + val key = FileUtil.getAlbumArtKey(entry) val request = ImageRequest.CoverArt( - id, view, requestedSize, + id, key, view, requestedSize, placeHolderDrawableRes = defaultResourceId, errorDrawableRes = defaultResourceId ) @@ -125,6 +126,7 @@ sealed class ImageRequest( ) { class CoverArt( val entityId: String, + val cacheKey: String, imageView: ImageView, val size: Int, placeHolderDrawableRes: Int? = null, @@ -150,7 +152,7 @@ sealed class ImageRequest( /** * Used to configure an instance of the ImageLoader */ -data class ImageLoaderConfig ( +data class ImageLoaderConfig( val largeSize: Int = 0, val defaultSize: Int = 0, val cacheFolder: File? diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/SubsonicImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/SubsonicImageLoader.kt deleted file mode 100644 index 712dd4df..00000000 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/SubsonicImageLoader.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.moire.ultrasonic.imageloader - -import android.content.Context -import com.squareup.picasso.Picasso -import com.squareup.picasso.RequestCreator -import org.moire.ultrasonic.BuildConfig -import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient - -class SubsonicImageLoader( - context: Context, - apiClient: SubsonicAPIClient -) { - private val picasso = Picasso.Builder(context) - .addRequestHandler(CoverArtRequestHandler(apiClient)) - .addRequestHandler(AvatarRequestHandler(apiClient)) - .build().apply { - setIndicatorsEnabled(BuildConfig.DEBUG) - Picasso.setSingletonInstance(this) - } - - fun load(request: ImageRequest) = when (request) { - is ImageRequest.CoverArt -> loadCoverArt(request) - is ImageRequest.Avatar -> loadAvatar(request) - } - - private fun loadCoverArt(request: ImageRequest.CoverArt) { - picasso.load(createLoadCoverArtRequest(request.entityId, request.size.toLong())) - .addPlaceholder(request) - .addError(request) - .stableKey("${request.entityId}-${request.size}") - .into(request.imageView) - } - - private fun loadAvatar(request: ImageRequest.Avatar) { - picasso.load(createLoadAvatarRequest(request.username)) - .addPlaceholder(request) - .addError(request) - .stableKey(request.username) - .into(request.imageView) - } - - private fun RequestCreator.addPlaceholder(request: ImageRequest): RequestCreator { - if (request.placeHolderDrawableRes != null) { - placeholder(request.placeHolderDrawableRes) - } - - return this - } - - private fun RequestCreator.addError(request: ImageRequest): RequestCreator { - if (request.errorDrawableRes != null) { - error(request.errorDrawableRes) - } - - return this - } -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt index 7b70a191..020d95d0 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt @@ -259,10 +259,9 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, override fun getCoverArt( entry: MusicDirectory.Entry, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? { - return musicService.getCoverArt(entry, size, saveToFile, highQuality) + return musicService.getCoverArt(entry, size, saveToFile) } @Throws(Exception::class) @@ -451,10 +450,9 @@ class CachedMusicService(private val musicService: MusicService) : MusicService, override fun getAvatar( username: String?, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? { - return musicService.getAvatar(username, size, saveToFile, highQuality) + return musicService.getAvatar(username, size, saveToFile) } companion object { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index fd98da9b..5babce2b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -24,6 +24,7 @@ import org.koin.core.component.inject import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService +import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.util.CacheCleaner import org.moire.ultrasonic.util.CancellableTask import org.moire.ultrasonic.util.FileUtil @@ -332,8 +333,9 @@ class DownloadFile( private fun downloadAndSaveCoverArt(musicService: MusicService) { try { if (!TextUtils.isEmpty(song.coverArt)) { - val size = Util.getMinDisplayMetric() - musicService.getCoverArt(song, size, true, true) + // Download the largest size that we can display in the UI + val size = ImageLoaderProvider.config.largeSize + musicService.getCoverArt(song, size = size, saveToFile = true) } } catch (e: Exception) { Timber.e(e, "Failed to get cover art.") 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 9a0b2938..3fd8f7e6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt @@ -30,6 +30,7 @@ import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.PlayerState import org.moire.ultrasonic.domain.RepeatMode +import org.moire.ultrasonic.imageloader.BitmapUtils import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1 import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2 import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3 @@ -37,7 +38,6 @@ import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4 import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.util.Constants -import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.NowPlayingEventDistributor import org.moire.ultrasonic.util.ShufflePlayBuffer import org.moire.ultrasonic.util.SimpleServiceBinder @@ -478,9 +478,8 @@ class MediaPlayerService : Service() { if (currentPlaying != null) { try { val song = currentPlaying.song - val cover = FileUtil.getAlbumArtBitmapFromDisk( - song, Util.getMinDisplayMetric(), - true + val cover = BitmapUtils.getAlbumArtBitmapFromDisk( + song, Util.getMinDisplayMetric() ) metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1L) metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist) @@ -648,7 +647,7 @@ class MediaPlayerService : Service() { // Set song title, artist and cover if possible if (song != null) { val iconSize = (256 * context.resources.displayMetrics.density).toInt() - val bitmap = FileUtil.getAlbumArtBitmapFromDisk(song, iconSize, true) + val bitmap = BitmapUtils.getAlbumArtBitmapFromDisk(song, iconSize) notificationBuilder!!.setContentTitle(song.title) notificationBuilder!!.setContentText(song.artist) notificationBuilder!!.setLargeIcon(bitmap) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt index 90f95f29..dc3b8cee 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt @@ -115,12 +115,15 @@ interface MusicService { fun getCoverArt( entry: MusicDirectory.Entry, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? @Throws(Exception::class) - fun getAvatar(username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean): Bitmap? + fun getAvatar( + username: String?, + size: Int, + saveToFile: Boolean + ): Bitmap? /** * Return response [InputStream] and a [Boolean] that indicates if this response is diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt index 788ca2d4..e6e0f425 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt @@ -40,6 +40,7 @@ import org.moire.ultrasonic.domain.SearchCriteria import org.moire.ultrasonic.domain.SearchResult import org.moire.ultrasonic.domain.Share import org.moire.ultrasonic.domain.UserInfo +import org.moire.ultrasonic.imageloader.BitmapUtils import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util @@ -121,11 +122,10 @@ class OfflineMusicService : MusicService, KoinComponent { override fun getAvatar( username: String?, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? { return try { - val bitmap = FileUtil.getAvatarBitmapFromDisk(username, size, highQuality) + val bitmap = BitmapUtils.getAvatarBitmapFromDisk(username, size) Util.scaleBitmap(bitmap, size) } catch (ignored: Exception) { null @@ -135,11 +135,10 @@ class OfflineMusicService : MusicService, KoinComponent { override fun getCoverArt( entry: MusicDirectory.Entry, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? { return try { - val bitmap = FileUtil.getAlbumArtBitmapFromDisk(entry, size, highQuality) + val bitmap = BitmapUtils.getAlbumArtBitmapFromDisk(entry, size) Util.scaleBitmap(bitmap, size) } catch (ignored: Exception) { null diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt index b2200672..013fb7e5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt @@ -46,6 +46,7 @@ import org.moire.ultrasonic.domain.toDomainEntitiesList import org.moire.ultrasonic.domain.toDomainEntity import org.moire.ultrasonic.domain.toDomainEntityList import org.moire.ultrasonic.domain.toMusicDirectoryDomainEntity +import org.moire.ultrasonic.imageloader.BitmapUtils import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util import timber.log.Timber @@ -488,21 +489,18 @@ open class RESTMusicService( return response.body()!!.starred2.toDomainEntity() } - // TODO: Implement file caching in Picasso CoverArtRequestHandler, - // and then use Picasso to handle this cache // This is only called by DownloadFile to cache the cover art for offline use @Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? { // Synchronize on the entry so that we don't download concurrently for // the same song. synchronized(entry) { // Use cached file, if existing. - var bitmap = FileUtil.getAlbumArtBitmapFromDisk(entry, size, highQuality) + var bitmap = BitmapUtils.getAlbumArtBitmapFromDisk(entry, size) val serverScaling = isServerScalingEnabled() if (bitmap == null) { @@ -541,7 +539,7 @@ open class RESTMusicService( } } - bitmap = FileUtil.getSampledBitmap(bytes, size, highQuality) + bitmap = BitmapUtils.getSampledBitmap(bytes, size) } finally { Util.close(inputStream) } @@ -820,8 +818,7 @@ open class RESTMusicService( override fun getAvatar( username: String?, size: Int, - saveToFile: Boolean, - highQuality: Boolean + saveToFile: Boolean ): Bitmap? { // Synchronize on the username so that we don't download concurrently for // the same user. @@ -831,7 +828,7 @@ open class RESTMusicService( synchronized(username) { // Use cached file, if existing. - var bitmap = FileUtil.getAvatarBitmapFromDisk(username, size, highQuality) + var bitmap = BitmapUtils.getAvatarBitmapFromDisk(username, size) if (bitmap == null) { var inputStream: InputStream? = null @@ -858,7 +855,7 @@ open class RESTMusicService( } } - bitmap = FileUtil.getSampledBitmap(bytes, size, highQuality) + bitmap = BitmapUtils.getSampledBitmap(bytes, size) } finally { Util.close(inputStream) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt index c674f1a6..6156b781 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt @@ -4,11 +4,12 @@ import android.content.Context import androidx.core.content.res.ResourcesCompat import org.koin.core.component.KoinComponent import org.koin.core.component.get +import org.koin.core.qualifier.named import org.moire.ultrasonic.R import org.moire.ultrasonic.app.UApp -import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.imageloader.ImageLoader import org.moire.ultrasonic.imageloader.ImageLoaderConfig +import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.Util /** @@ -16,26 +17,7 @@ import org.moire.ultrasonic.util.Util */ class ImageLoaderProvider(val context: Context) : KoinComponent { private var imageLoader: ImageLoader? = null - - private val config by lazy { - var defaultSize = 0 - val fallbackImage = ResourcesCompat.getDrawable( - UApp.applicationContext().resources, R.drawable.unknown_album, null - ) - - // Determine the density-dependent image sizes by taking the fallback album - // image and querying its size. - if (fallbackImage != null) { - defaultSize = fallbackImage.intrinsicHeight - } - - ImageLoaderConfig( - Util.getMaxDisplayMetric(), - defaultSize, - FileUtil.getAlbumArtDirectory() - ) - - } + private var serverID: String = get(named("ServerID")) @Synchronized fun clearImageLoader() { @@ -44,9 +26,33 @@ class ImageLoaderProvider(val context: Context) : KoinComponent { @Synchronized fun getImageLoader(): ImageLoader { - if (imageLoader == null) { - imageLoader = ImageLoader(get(), get(), config) + // We need to generate a new ImageLoader if the server has changed... + val currentID = get(named("ServerID")) + if (imageLoader == null || currentID != serverID) { + imageLoader = get() + serverID = currentID } return imageLoader!! } + + companion object { + val config by lazy { + var defaultSize = 0 + val fallbackImage = ResourcesCompat.getDrawable( + UApp.applicationContext().resources, R.drawable.unknown_album, null + ) + + // Determine the density-dependent image sizes by taking the fallback album + // image and querying its size. + if (fallbackImage != null) { + defaultSize = fallbackImage.intrinsicHeight + } + + ImageLoaderConfig( + Util.getMaxDisplayMetric(), + defaultSize, + FileUtil.getAlbumArtDirectory() + ) + } + } } diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt index 214cdad8..8ea94f82 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt @@ -64,7 +64,8 @@ class AvatarRequestHandlerTest { .thenReturn(streamResponse) val response = handler.load( - createLoadAvatarRequest("some-username").buildRequest(), 0) + createLoadAvatarRequest("some-username").buildRequest(), 0 + ) response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK response.source `should not be` null diff --git a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt index 5bd6c336..121a08da 100644 --- a/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt @@ -77,7 +77,8 @@ class CoverArtRequestHandlerTest { .thenReturn(streamResponse) val response = handler.load( - createLoadCoverArtRequest("some").buildRequest(), 0) + createLoadCoverArtRequest("some").buildRequest(), 0 + ) response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK response.source `should not be` null