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 75a908dac0..88b2e3855b 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 @@ -37,7 +37,7 @@ public final class GameRowPresenter extends Presenter ImageCardView gameCard = new ImageCardView(parent.getContext()); gameCard.setMainImageAdjustViewBounds(true); - gameCard.setMainImageDimensions(480, 320); + gameCard.setMainImageDimensions(240, 336); gameCard.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP); gameCard.setFocusable(true); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java index cd9fdf9598..fbdaf8ad92 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java @@ -199,6 +199,7 @@ public final class SettingsFragmentPresenter sl.add(new SubmenuSetting(null, null, R.string.gamecube_submenu, 0, MenuTag.CONFIG_GAME_CUBE)); sl.add(new SubmenuSetting(null, null, R.string.wii_submenu, 0, MenuTag.CONFIG_WII)); + sl.add(new HeaderSetting(null, null, R.string.gametdb_thanks, 0)); } private void addGeneralSettings(ArrayList sl) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java index 7e8628d4d2..b353a8126f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java @@ -1,7 +1,11 @@ package org.dolphinemu.dolphinemu.model; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.os.Environment; +import org.dolphinemu.dolphinemu.utils.CoverHelper; + public class GameFile { private long mPointer; // Do not rename or move without editing the native code @@ -19,12 +23,24 @@ public class GameFile public native String getDescription(); public native String getCompany(); public native int getCountry(); + public native int getRegion(); public native String getPath(); public native String getGameId(); public native int[] getBanner(); public native int getBannerWidth(); public native int getBannerHeight(); + public String getCoverPath() + { + return Environment.getExternalStorageDirectory().getPath() + + "/dolphin-emu/Cache/GameCovers/" + getGameId() + ".png"; + } + + public String getCustomCoverPath() + { + return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png"; + } + public String getScreenshotPath() { String gameId = getGameId(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java index 8333246f62..c51a8161be 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java @@ -12,8 +12,6 @@ import org.dolphinemu.dolphinemu.ui.platform.Platform; import java.io.File; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicReference; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java index 63330653b5..5df317019a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java @@ -154,6 +154,7 @@ public class SyncProgramsJobService extends JobService .setTitle(game.getTitle()) .setDescription(game.getDescription()) .setPosterArtUri(banner) + .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3) .setIntentUri(appLinkUri); return builder.build(); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index feb8827eef..1e3fb5ba8d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -2,7 +2,6 @@ package org.dolphinemu.dolphinemu.ui.main; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Build; import android.os.Bundle; import android.support.v17.leanback.app.BrowseFragment; import android.support.v17.leanback.app.BrowseSupportFragment; @@ -197,8 +196,6 @@ public final class TvMainActivity extends FragmentActivity implements MainView GameFileCacheService.startLoad(this); } - mRowsAdapter.add(buildSettingsRow()); - for (Platform platform : Platform.values()) { ListRow row = buildGamesRow(platform, GameFileCacheService.getGameFilesForPlatform(platform)); @@ -210,6 +207,8 @@ public final class TvMainActivity extends FragmentActivity implements MainView } } + mRowsAdapter.add(buildSettingsRow()); + mBrowseFragment.setAdapter(mRowsAdapter); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java new file mode 100644 index 0000000000..9e95409ee7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.java @@ -0,0 +1,81 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.graphics.Bitmap; + +import org.dolphinemu.dolphinemu.model.GameFile; +import org.dolphinemu.dolphinemu.ui.platform.Platform; + +import java.io.FileOutputStream; + +public final class CoverHelper +{ + private static String baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png"; + + public static String buildGameTDBUrl(GameFile game, String region) + { + String gameId = game.getGameId(); + if(game.getPlatform() == 2) // WiiWare + gameId = gameId.substring(0,4); + return String.format(baseUrl, region, gameId); + } + + public static String getRegion(GameFile game) + { + String region; + switch(game.getRegion()) + { + case 0: // NTSC_J + region = "JA"; + break; + case 1: // NTSC_U + region = "US"; + break; + case 4: // NTSC_K + region = "KO"; + break; + case 2: // PAL + switch (game.getCountry()) + { + case 2: // German + region = "DE"; + break; + case 3: // French + region = "FR"; + break; + case 4: // Spanish + region = "ES"; + break; + case 5: // Italian + region = "IT"; + break; + case 6: // Dutch + region = "NL"; + break; + case 1: // English + default: + region = "EN"; + break; + } + break; + case 3: // Unknown + default: + region = "EN"; + break; + } + return region; + } + + public static void saveCover(Bitmap cover, String path) + { + try + { + FileOutputStream out = new FileOutputStream(path); + cover.compress(Bitmap.CompressFormat.PNG, 100, out); + out.close(); + } + catch (Exception e) + { + // Do nothing + } + } +} \ No newline at end of file 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 b8afcf6a52..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -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.NativeLibrary; -import org.dolphinemu.dolphinemu.model.GameFile; - -import java.io.IOException; -import java.nio.IntBuffer; - -public class GameBannerRequestHandler extends RequestHandler { - 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/PicassoUtils.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java index 4630a3cddc..06f03f2154 100644 --- 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 @@ -1,46 +1,114 @@ package org.dolphinemu.dolphinemu.utils; import android.graphics.Bitmap; -import android.net.Uri; +import android.graphics.drawable.BitmapDrawable; import android.widget.ImageView; +import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.model.GameFile; import java.io.File; -import java.net.URI; public class PicassoUtils { - public static void loadGameBanner(ImageView imageView, GameFile gameFile) { - File screenshotFile = new File(URI.create(gameFile.getScreenshotPath())); - if (screenshotFile.exists()) { - // Fill in the view contents. + public static void loadGameBanner(ImageView imageView, GameFile gameFile) + { + File cover = new File(gameFile.getCustomCoverPath()); + if (cover.exists()) + { Picasso.with(imageView.getContext()) - .load(gameFile.getScreenshotPath()) - .fit() - .centerCrop() - .noFade() - .noPlaceholder() - .config(Bitmap.Config.RGB_565) - .error(R.drawable.no_banner) - .into(imageView); - } else { - 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); + .load(cover) + .fit() + .centerCrop() + .noFade() + .noPlaceholder() + .config(Bitmap.Config.ARGB_8888) + .error(R.drawable.no_banner) + .into(imageView); + } + else if ((cover = new File(gameFile.getCoverPath())).exists()) + { + Picasso.with(imageView.getContext()) + .load(cover) + .fit() + .centerCrop() + .noFade() + .noPlaceholder() + .config(Bitmap.Config.ARGB_8888) + .error(R.drawable.no_banner) + .into(imageView); + } + /** + * 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 + { + Picasso.with(imageView.getContext()) + .load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile))) + .fit() + .centerCrop() + .noFade() + .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()); + } + @Override + public void onError() // Second pass using US region + { + Picasso.with(imageView.getContext()) + .load(CoverHelper.buildGameTDBUrl(gameFile, "US")) + .fit() + .centerCrop() + .noFade() + .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()); + } + @Override + public void onError() // Third and last pass using EN region + { + Picasso.with(imageView.getContext()) + .load(CoverHelper.buildGameTDBUrl(gameFile, "EN")) + .fit() + .centerCrop() + .noFade() + .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()); + } + @Override + public void onError() + { + } + }); + } + }); + } + }); } - - } } 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 a07b00e456..7597871e83 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 @@ -154,8 +154,8 @@ public class TvUtil } /** - * Leanback lanucher requires a uri for poster art, so we take the banner vector, - * make a bitmap, save that bitmap, then return the file provider uri. + * Leanback lanucher requires a uri for poster art so we create a contentUri and + * pass that to LEANBACK_PACKAGE */ public static Uri buildBanner(GameFile game, Context context) { @@ -163,33 +163,14 @@ public class TvUtil try { - //Substring needed to strip "file:" from the path beginning - File screenshotFile = new File(game.getScreenshotPath().substring(5)); - if (screenshotFile.exists()) + File cover = new File(game.getCustomCoverPath()); + if(cover.exists()) { - contentUri = getUriForFile(context, getFilePrivider(context), screenshotFile); + contentUri = getUriForFile(context, getFileProvider(context), cover); } - else + else if ((cover = new File(game.getCoverPath())).exists()) { - File file = new File(buildBannerFilename(game.getGameId())); - if (!file.exists()) - { - int[] vector = game.getBanner(); - int width = game.getBannerWidth(); - int height = game.getBannerHeight(); - - if (vector.length > 0 || width > 0 || height > 0) - { - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.setPixels(vector, 0, width, 0, 0, width, height); - FileOutputStream out = new FileOutputStream(file); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.close(); - } - else - return null; - } - contentUri = getUriForFile(context, getFilePrivider(context), file); + contentUri = getUriForFile(context, getFileProvider(context), cover); } context.grantUriPermission(LEANBACK_PACKAGE, contentUri, FLAG_GRANT_READ_URI_PERMISSION); @@ -203,16 +184,10 @@ public class TvUtil return contentUri; } - private static String buildBannerFilename(String gameId) - { - return Environment.getExternalStorageDirectory().getPath() + - "/dolphin-emu/Cache/" + gameId + "_banner.png"; - } - /** * Needed since debug builds append '.debug' to the end of the package */ - private static String getFilePrivider(Context context) + private static String getFileProvider(Context context) { return context.getPackageName() + ".filesprovider"; } diff --git a/Source/Android/app/src/main/res/layout/card_game.xml b/Source/Android/app/src/main/res/layout/card_game.xml index 1ee76e3471..488fda5232 100644 --- a/Source/Android/app/src/main/res/layout/card_game.xml +++ b/Source/Android/app/src/main/res/layout/card_game.xml @@ -2,8 +2,8 @@ - 4 + 6 \ No newline at end of file diff --git a/Source/Android/app/src/main/res/values-w500dp/integers.xml b/Source/Android/app/src/main/res/values-w500dp/integers.xml index d2955c0ae3..f049d8b44e 100644 --- a/Source/Android/app/src/main/res/values-w500dp/integers.xml +++ b/Source/Android/app/src/main/res/values-w500dp/integers.xml @@ -1,4 +1,4 @@ - 2 + 3 \ No newline at end of file diff --git a/Source/Android/app/src/main/res/values-w750dp/integers.xml b/Source/Android/app/src/main/res/values-w750dp/integers.xml index f049d8b44e..5cd4e24f38 100644 --- a/Source/Android/app/src/main/res/values-w750dp/integers.xml +++ b/Source/Android/app/src/main/res/values-w750dp/integers.xml @@ -1,4 +1,4 @@ - 3 + 4 \ No newline at end of file diff --git a/Source/Android/app/src/main/res/values/integers.xml b/Source/Android/app/src/main/res/values/integers.xml index 131c9d05f3..dfec4d8361 100644 --- a/Source/Android/app/src/main/res/values/integers.xml +++ b/Source/Android/app/src/main/res/values/integers.xml @@ -1,6 +1,6 @@ - 1 + 2 865 @@ -81,4 +81,4 @@ 429 737 311 - \ No newline at end of file + diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index e22775ad0b..1121ff0f64 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -135,6 +135,7 @@ Enable sound output through the speaker on a real Wiimote (DolphinBar required). Audio Stretching Stretches audio to reduce stuttering. Increases latency. + Thanks to GameTDB.com for providing GameCube and Wii covers! Interface diff --git a/Source/Android/jni/GameList/GameFile.cpp b/Source/Android/jni/GameList/GameFile.cpp index b2cf26cd52..c11afa8c60 100644 --- a/Source/Android/jni/GameList/GameFile.cpp +++ b/Source/Android/jni/GameList/GameFile.cpp @@ -52,6 +52,8 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCompa jobject obj); JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCountry(JNIEnv* env, jobject obj); +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRegion(JNIEnv* env, + jobject obj); JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath(JNIEnv* env, jobject obj); JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getGameId(JNIEnv* env, @@ -99,6 +101,12 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCountry( return static_cast(GetRef(env, obj)->GetCountry()); } +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRegion(JNIEnv* env, + jobject obj) +{ + return static_cast(GetRef(env, obj)->GetRegion()); +} + JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath(JNIEnv* env, jobject obj) {