diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java index 62193569..e339cf1d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java +++ b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java @@ -29,24 +29,127 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.target.Target; -import java.util.ArrayList; -import java.util.List; - import code.name.monkey.retromusic.App; import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.glide.artistimage.AlbumCover; import code.name.monkey.retromusic.glide.artistimage.ArtistImage; import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; -import code.name.monkey.retromusic.model.Album; import code.name.monkey.retromusic.model.Artist; -import code.name.monkey.retromusic.model.Song; import code.name.monkey.retromusic.util.ArtistSignatureUtil; import code.name.monkey.retromusic.util.CustomArtistImageUtil; + +public class ArtistGlideRequest { + + public static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art; + + public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage, boolean forceDownload) { + boolean hasCustomImage = CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()).hasCustomArtistImage(artist); + if (noCustomImage || !hasCustomImage) { + return requestManager.load(new ArtistImage(artist.getName(), forceDownload)); + } else { + return requestManager.load(CustomArtistImageUtil.getFile(artist)); + } + } + + public static Key createSignature(Artist artist) { + return ArtistSignatureUtil.getInstance(App.Companion.getContext()).getArtistSignature(artist.getName()); + } + + public static class Builder { + final RequestManager requestManager; + final Artist artist; + boolean noCustomImage; + boolean forceDownload; + + private Builder(@NonNull RequestManager requestManager, Artist artist) { + this.requestManager = requestManager; + this.artist = artist; + } + + public static Builder from(@NonNull RequestManager requestManager, Artist artist) { + return new Builder(requestManager, artist); + } + + public PaletteBuilder generatePalette(Context context) { + return new PaletteBuilder(this, context); + } + + public BitmapBuilder asBitmap() { + return new BitmapBuilder(this); + } + + public Builder noCustomImage(boolean noCustomImage) { + this.noCustomImage = noCustomImage; + return this; + } + + public Builder forceDownload(boolean forceDownload) { + this.forceDownload = forceDownload; + return this; + } + + public DrawableRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(artist)); + } + } + + public static class BitmapBuilder { + private final Builder builder; + + public BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(builder.artist)); + } + } + + public static class PaletteBuilder { + final Context context; + private final Builder builder; + + public PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(builder.artist)); + } + } +} + /** * @author Karim Abou Zeid (kabouzeid) - */ + *//* public class ArtistGlideRequest { public static final int DEFAULT_ANIMATION = android.R.anim.fade_in; @@ -152,4 +255,4 @@ public class ArtistGlideRequest { .signature(createSignature(builder.artist)); } } -} +}*/ diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt index da9620f9..816a324f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.kt @@ -17,6 +17,7 @@ package code.name.monkey.retromusic.glide import android.content.Context import code.name.monkey.retromusic.glide.artistimage.ArtistImage import code.name.monkey.retromusic.glide.artistimage.ArtistImageLoader +import code.name.monkey.retromusic.glide.artistimage.Factory import code.name.monkey.retromusic.glide.audiocover.AudioFileCover import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverLoader import com.bumptech.glide.Glide @@ -30,6 +31,6 @@ class RetroMusicGlideModule : GlideModule { override fun registerComponents(context: Context, glide: Glide) { glide.register(AudioFileCover::class.java, InputStream::class.java, AudioFileCoverLoader.Factory()) - glide.register(ArtistImage::class.java, InputStream::class.java, ArtistImageLoader.Factory()) + glide.register(ArtistImage::class.java, InputStream::class.java, Factory(context)) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt index 40e21d8d..270b0466 100644 --- a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.kt @@ -15,25 +15,131 @@ package code.name.monkey.retromusic.glide.artistimage import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.media.MediaMetadataRetriever -import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverUtils -import code.name.monkey.retromusic.util.ImageUtil +import code.name.monkey.retromusic.deezer.Data +import code.name.monkey.retromusic.deezer.DeezerApiService +import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import com.bumptech.glide.Priority +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.model.GenericLoaderFactory +import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.ModelLoader import com.bumptech.glide.load.model.ModelLoaderFactory import com.bumptech.glide.load.model.stream.StreamModelLoader -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream +import okhttp3.OkHttpClient import java.io.IOException import java.io.InputStream +import java.util.concurrent.TimeUnit -class AlbumCover( +class ArtistImage(val artistName: String, val skipOkHttpCache: Boolean) + +class ArtistImageFetcher( + private val context: Context, + private val deezerApiService: DeezerApiService, + val model: ArtistImage, + val urlLoader: ModelLoader, + val width: Int, + val height: Int +) : DataFetcher { + + private var urlFetcher: DataFetcher? = null + private var isCancelled: Boolean = false + + override fun cleanup() { + urlFetcher?.cleanup() + } + + override fun getId(): String { + return model.artistName + } + + override fun cancel() { + isCancelled = true + urlFetcher?.cancel() + } + + override fun loadData(priority: Priority?): InputStream? { + if (!MusicUtil.isArtistNameUnknown(model.artistName) && PreferenceUtil.isAllowedToDownloadMetadata(context)) { + val artists = model.artistName.split(",|&") + val response = deezerApiService.getArtistImage(artists[0]).execute() + + if (!response.isSuccessful) { + throw IOException("Request failed with code: " + response.code()); + } + + if (isCancelled) return null + + return try { + val deezerResponse = response.body(); + val imageUrl = deezerResponse?.data?.get(0)?.let { getHighestQuality(it) } + val glideUrl = GlideUrl(imageUrl) + urlFetcher = urlLoader.getResourceFetcher(glideUrl, width, height) + urlFetcher?.loadData(priority) + } catch (e: Exception) { + null + } + } else return null + } + + private fun getHighestQuality(imageUrl: Data): String { + return when { + imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl + imageUrl.pictureBig.isNotEmpty() -> imageUrl.pictureBig + imageUrl.pictureMedium.isNotEmpty() -> imageUrl.pictureMedium + imageUrl.pictureSmall.isNotEmpty() -> imageUrl.pictureSmall + imageUrl.picture.isNotEmpty() -> imageUrl.picture + else -> "" + } + } +} + +class ArtistImageLoader( + val context: Context, + val deezerApiService: DeezerApiService, + val urlLoader: ModelLoader +) : StreamModelLoader { + + override fun getResourceFetcher(model: ArtistImage, width: Int, height: Int): DataFetcher { + return ArtistImageFetcher(context, deezerApiService, model, urlLoader, width, height) + } +} + +class Factory( + val context: Context +) : ModelLoaderFactory { + private var deezerApiService: DeezerApiService + private var okhttpFactory: OkHttpUrlLoader.Factory + + init { + okhttpFactory = OkHttpUrlLoader.Factory(OkHttpClient.Builder() + .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .build()) + deezerApiService = DeezerApiService.invoke(DeezerApiService.createDefaultOkHttpClient(context) + .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .build()) + } + + override fun build(context: Context?, factories: GenericLoaderFactory?): ModelLoader { + return ArtistImageLoader(context!!, deezerApiService, okhttpFactory.build(context, factories)) + } + + override fun teardown() { + okhttpFactory.teardown() + } + + companion object { + // we need these very low values to make sure our artist image loading calls doesn't block the image loading queue + private const val TIMEOUT: Long = 700 + } + +} +/*class AlbumCover( var year: Int, var filePath: String?) @@ -207,7 +313,7 @@ class ArtistImageLoader( } } -} +}*/ /* class ArtistImage(val artistName: String, val skipOkHttpCache: Boolean) diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java index c269e42e..5017139f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java @@ -19,6 +19,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.TypedArray; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; import android.preference.PreferenceManager; @@ -142,11 +144,24 @@ public final class PreferenceUtil { private static PreferenceUtil sInstance; private final SharedPreferences mPreferences; - private PreferenceUtil(@NonNull final Context context) { mPreferences = PreferenceManager.getDefaultSharedPreferences(context); } + public static boolean isAllowedToDownloadMetadata(final Context context) { + switch (getInstance(context).autoDownloadImagesPolicy()) { + case "always": + return true; + case "only_wifi": + final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); + return netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo.isConnectedOrConnecting(); + case "never": + default: + return false; + } + } + @NonNull public static PreferenceUtil getInstance(Context context) { if (sInstance == null) {