diff --git a/Source/Android/app/build.gradle b/Source/Android/app/build.gradle index adae476cf4..db568618a5 100644 --- a/Source/Android/app/build.gradle +++ b/Source/Android/app/build.gradle @@ -112,8 +112,8 @@ dependencies { // For REST calls implementation 'com.android.volley:volley:1.2.1' - // For loading huge screenshots from the disk. - implementation 'com.squareup.picasso:picasso:2.71828' + // For loading game covers from disk and GameTDB + implementation 'com.github.bumptech.glide:glide:4.13.1' implementation 'com.nononsenseapps:filepicker:4.2.1' } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java index e35645ee60..9d45036e11 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java @@ -17,7 +17,7 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.services.GameFileCacheManager; -import org.dolphinemu.dolphinemu.utils.PicassoUtils; +import org.dolphinemu.dolphinemu.utils.GlideUtils; import org.dolphinemu.dolphinemu.viewholders.GameViewHolder; import java.util.ArrayList; @@ -72,7 +72,7 @@ public final class GameAdapter extends RecyclerView.Adapter impl { Context context = holder.itemView.getContext(); GameFile gameFile = mGameFiles.get(position); - PicassoUtils.loadGameCover(holder, holder.imageScreenshot, gameFile); + GlideUtils.loadGameCover(holder, holder.imageScreenshot, gameFile); if (GameFileCacheManager.findSecondDisc(gameFile) != null) { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java index 6261ea6827..b1a0665407 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java @@ -16,7 +16,7 @@ import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.dialogs.GamePropertiesDialog; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.services.GameFileCacheManager; -import org.dolphinemu.dolphinemu.utils.PicassoUtils; +import org.dolphinemu.dolphinemu.utils.GlideUtils; import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; /** @@ -50,7 +50,7 @@ public final class GameRowPresenter extends Presenter GameFile gameFile = (GameFile) item; holder.imageScreenshot.setImageDrawable(null); - PicassoUtils.loadGameCover(null, holder.imageScreenshot, gameFile); + GlideUtils.loadGameCover(null, holder.imageScreenshot, gameFile); holder.cardParent.setTitleText(gameFile.getTitle()); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java index 4da860ddc4..f049fd97a8 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java @@ -17,7 +17,7 @@ import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.services.GameFileCacheManager; -import org.dolphinemu.dolphinemu.utils.PicassoUtils; +import org.dolphinemu.dolphinemu.utils.GlideUtils; public final class GameDetailsDialog extends DialogFragment { @@ -114,7 +114,7 @@ public final class GameDetailsDialog extends DialogFragment } } - PicassoUtils.loadGameBanner(banner, gameFile); + GlideUtils.loadGameBanner(banner, gameFile); return new MaterialAlertDialogBuilder(requireActivity()) .setView(contents) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java deleted file mode 100644 index fdad65a050..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import android.graphics.Bitmap; - -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Request; -import com.squareup.picasso.RequestHandler; - -import org.dolphinemu.dolphinemu.model.GameFile; - -public class GameBannerRequestHandler extends RequestHandler -{ - private final GameFile mGameFile; - - public GameBannerRequestHandler(GameFile gameFile) - { - mGameFile = gameFile; - } - - @Override - public boolean canHandleRequest(Request data) - { - return true; - } - - @Override - public Result load(Request request, int networkPolicy) - { - int[] vector = mGameFile.getBanner(); - int width = mGameFile.getBannerWidth(); - int height = mGameFile.getBannerHeight(); - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.setPixels(vector, 0, width, 0, 0, width, height); - return new Result(bitmap, Picasso.LoadedFrom.DISK); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java new file mode 100644 index 0000000000..f3a626bebd --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GlideUtils.java @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.view.View; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; +import org.dolphinemu.dolphinemu.model.GameFile; +import org.dolphinemu.dolphinemu.viewholders.GameViewHolder; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class GlideUtils +{ + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + + public static void loadGameBanner(ImageView imageView, GameFile gameFile) + { + Context context = imageView.getContext(); + int[] vector = gameFile.getBanner(); + int width = gameFile.getBannerWidth(); + int height = gameFile.getBannerHeight(); + if (width > 0 && height > 0) + { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(vector, 0, width, 0, 0, width, height); + Glide.with(context) + .load(bitmap) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .centerCrop() + .into(imageView); + } + else + { + Glide.with(context) + .load(R.drawable.no_banner) + .into(imageView); + } + } + + public static void loadGameCover(GameViewHolder gameViewHolder, ImageView imageView, + GameFile gameFile) + { + if (BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal() && gameViewHolder != null) + { + gameViewHolder.textGameTitle.setText(gameFile.getTitle()); + gameViewHolder.textGameTitle.setVisibility(View.VISIBLE); + gameViewHolder.textGameTitleInner.setVisibility(View.GONE); + gameViewHolder.textGameCaption.setVisibility(View.VISIBLE); + } + else if (gameViewHolder != null) + { + gameViewHolder.textGameTitleInner.setText(gameFile.getTitle()); + gameViewHolder.textGameTitle.setVisibility(View.GONE); + gameViewHolder.textGameCaption.setVisibility(View.GONE); + } + + String customCoverPath = gameFile.getCustomCoverPath(); + Uri customCoverUri = null; + boolean customCoverExists = false; + if (ContentHandler.isContentUri(customCoverPath)) + { + try + { + customCoverUri = ContentHandler.unmangle(customCoverPath); + customCoverExists = true; + } + catch (FileNotFoundException | SecurityException ignored) + { + // Let customCoverExists remain false + } + } + else + { + customCoverUri = Uri.parse(customCoverPath); + customCoverExists = new File(customCoverPath).exists(); + } + + Context context = imageView.getContext(); + File cover; + if (customCoverExists) + { + Glide.with(context) + .load(customCoverUri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .centerCrop() + .listener(new RequestListener() + { + @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, + Target target, boolean isFirstResource) + { + GlideUtils.enableInnerTitle(gameViewHolder, imageView); + return false; + } + + @Override public boolean onResourceReady(Drawable resource, Object model, + Target target, DataSource dataSource, boolean isFirstResource) + { + GlideUtils.disableInnerTitle(gameViewHolder); + return false; + } + }) + .into(imageView); + } + else if ((cover = new File(gameFile.getCoverPath(context))).exists()) + { + Glide.with(context) + .load(cover) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .centerCrop() + .listener(new RequestListener() + { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, + Target target, boolean isFirstResource) + { + GlideUtils.enableInnerTitle(gameViewHolder, imageView); + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, + Target target, DataSource dataSource, boolean isFirstResource) + { + GlideUtils.disableInnerTitle(gameViewHolder); + return false; + } + }) + .into(imageView); + } + else if (BooleanSetting.MAIN_USE_GAME_COVERS.getBooleanGlobal()) + { + Glide.with(context) + .load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile))) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .centerCrop() + .listener(new RequestListener() + { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, + Target target, boolean isFirstResource) + { + GlideUtils.enableInnerTitle(gameViewHolder, imageView); + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, + Target target, DataSource dataSource, boolean isFirstResource) + { + GlideUtils.disableInnerTitle(gameViewHolder); + return false; + } + }) + .into(new CustomTarget() + { + @Override + public void onResourceReady(@NonNull Drawable resource, + @Nullable Transition transition) + { + Bitmap cover = ((BitmapDrawable) resource).getBitmap(); + executor.execute( + () -> CoverHelper.saveCover(cover, gameFile.getCoverPath(context))); + imageView.setImageBitmap(cover); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) + { + } + }); + } + else + { + enableInnerTitle(gameViewHolder, imageView); + } + } + + private static void enableInnerTitle(GameViewHolder gameViewHolder, ImageView imageView) + { + Glide.with(imageView.getContext()) + .load(R.drawable.no_banner) + .into(imageView); + + if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal()) + { + gameViewHolder.textGameTitleInner.setVisibility(View.VISIBLE); + } + } + + private static void disableInnerTitle(GameViewHolder gameViewHolder) + { + if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal()) + { + gameViewHolder.textGameTitleInner.setVisibility(View.GONE); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java deleted file mode 100644 index bde8b1eb69..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.net.Uri; -import android.view.View; -import android.widget.ImageView; - -import com.squareup.picasso.Callback; -import com.squareup.picasso.Picasso; - -import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; -import org.dolphinemu.dolphinemu.model.GameFile; -import org.dolphinemu.dolphinemu.viewholders.GameViewHolder; - -import java.io.File; -import java.io.FileNotFoundException; - -public class PicassoUtils -{ - public static void loadGameBanner(ImageView imageView, GameFile gameFile) - { - Picasso picassoInstance = new Picasso.Builder(imageView.getContext()) - .addRequestHandler(new GameBannerRequestHandler(gameFile)) - .build(); - - picassoInstance - .load(Uri.parse("iso:/" + gameFile.getPath())) - .fit() - .noFade() - .noPlaceholder() - .config(Bitmap.Config.RGB_565) - .error(R.drawable.no_banner) - .into(imageView); - } - - public static void loadGameCover(GameViewHolder gameViewHolder, ImageView imageView, - GameFile gameFile) - { - if (BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal() && gameViewHolder != null) - { - gameViewHolder.textGameTitle.setText(gameFile.getTitle()); - gameViewHolder.textGameTitle.setVisibility(View.VISIBLE); - gameViewHolder.textGameTitleInner.setVisibility(View.GONE); - gameViewHolder.textGameCaption.setVisibility(View.VISIBLE); - } - else if (gameViewHolder != null) - { - gameViewHolder.textGameTitleInner.setText(gameFile.getTitle()); - gameViewHolder.textGameTitle.setVisibility(View.GONE); - gameViewHolder.textGameCaption.setVisibility(View.GONE); - } - - String customCoverPath = gameFile.getCustomCoverPath(); - Uri customCoverUri = null; - boolean customCoverExists = false; - if (ContentHandler.isContentUri(customCoverPath)) - { - try - { - customCoverUri = ContentHandler.unmangle(customCoverPath); - customCoverExists = true; - } - catch (FileNotFoundException | SecurityException ignored) - { - // Let customCoverExists remain false - } - } - else - { - customCoverUri = Uri.parse(customCoverPath); - customCoverExists = new File(customCoverPath).exists(); - } - - Context context = imageView.getContext(); - File cover; - if (customCoverExists) - { - Picasso.get() - .load(customCoverUri) - .noFade() - .noPlaceholder() - .fit() - .centerInside() - .config(Bitmap.Config.ARGB_8888) - .error(R.drawable.no_banner) - .into(imageView, new Callback() - { - @Override public void onSuccess() - { - PicassoUtils.disableInnerTitle(gameViewHolder); - } - - @Override public void onError(Exception e) - { - PicassoUtils.enableInnerTitle(gameViewHolder, gameFile); - } - }); - } - else if ((cover = new File(gameFile.getCoverPath(context))).exists()) - { - Picasso.get() - .load(cover) - .noFade() - .noPlaceholder() - .fit() - .centerInside() - .config(Bitmap.Config.ARGB_8888) - .error(R.drawable.no_banner) - .into(imageView, new Callback() - { - @Override public void onSuccess() - { - PicassoUtils.disableInnerTitle(gameViewHolder); - } - - @Override public void onError(Exception e) - { - PicassoUtils.enableInnerTitle(gameViewHolder, gameFile); - } - }); - } - // GameTDB has a pretty close to complete collection for US/EN covers. First pass at getting - // the cover will be by the disk's region, second will be the US cover, and third EN. - else if (BooleanSetting.MAIN_USE_GAME_COVERS.getBooleanGlobal()) - { - Picasso.get() - .load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile))) - .noFade() - .noPlaceholder() - .fit() - .centerInside() - .config(Bitmap.Config.ARGB_8888) - .error(R.drawable.no_banner) - .into(imageView, new Callback() - { - @Override - public void onSuccess() - { - CoverHelper.saveCover(((BitmapDrawable) imageView.getDrawable()).getBitmap(), - gameFile.getCoverPath(context)); - PicassoUtils.disableInnerTitle(gameViewHolder); - } - - @Override - public void onError(Exception ex) // Second pass using US region - { - Picasso.get() - .load(CoverHelper.buildGameTDBUrl(gameFile, "US")) - .fit() - .noFade() - .fit() - .centerInside() - .noPlaceholder() - .config(Bitmap.Config.ARGB_8888) - .error(R.drawable.no_banner) - .into(imageView, new Callback() - { - @Override - public void onSuccess() - { - CoverHelper.saveCover( - ((BitmapDrawable) imageView.getDrawable()).getBitmap(), - gameFile.getCoverPath(context)); - PicassoUtils.disableInnerTitle(gameViewHolder); - } - - @Override - public void onError(Exception ex) // Third and last pass using EN region - { - Picasso.get() - .load(CoverHelper.buildGameTDBUrl(gameFile, "EN")) - .fit() - .noFade() - .fit() - .centerInside() - .noPlaceholder() - .config(Bitmap.Config.ARGB_8888) - .error(R.drawable.no_banner) - .into(imageView, new Callback() - { - @Override - public void onSuccess() - { - CoverHelper.saveCover( - ((BitmapDrawable) imageView.getDrawable()) - .getBitmap(), - gameFile.getCoverPath(context)); - PicassoUtils.disableInnerTitle(gameViewHolder); - } - - @Override - public void onError(Exception ex) - { - PicassoUtils.enableInnerTitle(gameViewHolder, gameFile); - } - }); - } - }); - } - }); - } - else - { - Picasso.get() - .load(R.drawable.no_banner) - .noFade() - .noPlaceholder() - .fit() - .centerInside() - .config(Bitmap.Config.ARGB_8888) - .into(imageView, new Callback() - { - @Override public void onSuccess() - { - PicassoUtils.disableInnerTitle(gameViewHolder); - } - - @Override public void onError(Exception e) - { - PicassoUtils.enableInnerTitle(gameViewHolder, gameFile); - } - }); - } - } - - private static void enableInnerTitle(GameViewHolder gameViewHolder, GameFile gameFile) - { - if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal()) - { - gameViewHolder.textGameTitleInner.setVisibility(View.VISIBLE); - } - } - - private static void disableInnerTitle(GameViewHolder gameViewHolder) - { - if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal()) - { - gameViewHolder.textGameTitleInner.setVisibility(View.GONE); - } - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java index b091ed6423..940542ef49 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/TvUtil.java @@ -16,7 +16,6 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; -import android.media.tv.TvContract; import android.net.Uri; import android.os.Build; import android.os.PersistableBundle;