From 0e52d11ede15d0a47761dce42ebf1a8f7eb83719 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sun, 12 Feb 2023 00:17:19 -0800 Subject: [PATCH] android: frontend: Implement game grid view. (#9) --- src/android/app/build.gradle | 2 +- .../java/org/yuzu/yuzu_emu/NativeLibrary.java | 9 +- .../yuzu/yuzu_emu/adapters/GameAdapter.java | 8 +- .../java/org/yuzu/yuzu_emu/model/Game.java | 2 +- .../org/yuzu/yuzu_emu/model/GameDatabase.java | 5 +- .../yuzu/yuzu_emu/ui/main/MainActivity.java | 2 + .../ui/platform/PlatformGamesFragment.java | 35 ++- .../utils/GameIconRequestHandler.java | 12 +- .../org/yuzu/yuzu_emu/utils/PicassoUtils.java | 2 +- .../yuzu_emu/viewholders/GameViewHolder.java | 6 +- src/android/app/src/main/jni/native.cpp | 211 ++++++++++++------ src/android/app/src/main/jni/native.h | 8 +- .../app/src/main/res/layout/card_game.xml | 119 +++++----- .../app/src/main/res/layout/fragment_grid.xml | 23 +- .../app/src/main/res/values/dimens.xml | 2 +- 15 files changed, 272 insertions(+), 174 deletions(-) diff --git a/src/android/app/build.gradle b/src/android/app/build.gradle index c516b2bff..74835113c 100644 --- a/src/android/app/build.gradle +++ b/src/android/app/build.gradle @@ -11,7 +11,7 @@ def abiFilter = "arm64-v8a" //, "x86" android { compileSdkVersion 32 - ndkVersion "25.1.8937393" + ndkVersion "25.2.9519653" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java index 75395bd4c..7a1ddd38e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java @@ -141,9 +141,9 @@ public final class NativeLibrary { * Gets the embedded icon within the given ROM. * * @param filename the file path to the ROM. - * @return an integer array containing the color data for the icon. + * @return a byte array containing the JPEG data for the icon. */ - public static native int[] GetIcon(String filename); + public static native byte[] GetIcon(String filename); /** * Gets the embedded title of the given ISO/ROM. @@ -204,6 +204,11 @@ public final class NativeLibrary { */ public static native void StopEmulation(); + /** + * Resets the in-memory ROM metadata cache. + */ + public static native void ResetRomMetadata(); + /** * Returns true if emulation is running (or is paused). */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java index cd9f823d4..ed1a000c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java @@ -86,11 +86,7 @@ public final class GameAdapter extends RecyclerView.Adapter impl mCursor.getString(GameDatabase.GAME_COLUMN_PATH)); holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " ")); - holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY)); - - String filepath = mCursor.getString(GameDatabase.GAME_COLUMN_PATH); - String filename = FileUtil.getFilename(YuzuApplication.getAppContext(), filepath); - holder.textFileName.setText(filename); + holder.textGameCaption.setText(mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION)); // TODO These shouldn't be necessary once the move to a DB-based model is complete. holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID); @@ -98,7 +94,7 @@ public final class GameAdapter extends RecyclerView.Adapter impl holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE); holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION); holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS); - holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY); + holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION); final int backgroundColorId = isValidGame(holder.path) ? R.color.view_background : R.color.view_disabled; View itemView = holder.getItemView(); diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java index bc1b19bd1..681117268 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java @@ -47,7 +47,7 @@ public final class Game { cursor.getString(GameDatabase.GAME_COLUMN_REGIONS), cursor.getString(GameDatabase.GAME_COLUMN_PATH), cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID), - cursor.getString(GameDatabase.GAME_COLUMN_COMPANY)); + cursor.getString(GameDatabase.GAME_COLUMN_CAPTION)); } public String getTitle() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java index 771e35c69..a10ac6ff2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java @@ -29,7 +29,7 @@ public final class GameDatabase extends SQLiteOpenHelper { public static final int GAME_COLUMN_DESCRIPTION = 3; public static final int GAME_COLUMN_REGIONS = 4; public static final int GAME_COLUMN_GAME_ID = 5; - public static final int GAME_COLUMN_COMPANY = 6; + public static final int GAME_COLUMN_CAPTION = 6; public static final int FOLDER_COLUMN_PATH = 1; public static final String KEY_DB_ID = "_id"; public static final String KEY_GAME_PATH = "path"; @@ -176,6 +176,9 @@ public final class GameDatabase extends SQLiteOpenHelper { return; } + // Ensure keys are loaded so that ROM metadata can be decrypted. + NativeLibrary.ReloadKeys(); + MinimalDocumentFile[] children = FileUtil.listFiles(context, parent); for (MinimalDocumentFile file : children) { if (file.isDirectory()) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java index 7fdd692c2..552232bd3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java @@ -161,6 +161,7 @@ public final class MainActivity extends AppCompatActivity implements MainView { if (FileUtil.copyUriToInternalStorage(this, result.getData(), dstPath, "prod.keys")) { if (NativeLibrary.ReloadKeys()) { Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show(); + refreshFragment(); } else { Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show(); launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); @@ -184,6 +185,7 @@ public final class MainActivity extends AppCompatActivity implements MainView { private void refreshFragment() { if (mPlatformGamesFragment != null) { + NativeLibrary.ResetRomMetadata(); mPlatformGamesFragment.refresh(); } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java index 6c327b1b8..2d74f43ca 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java @@ -5,6 +5,7 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.TextView; import androidx.core.content.ContextCompat; @@ -13,6 +14,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import org.yuzu.yuzu_emu.NativeLibrary; import org.yuzu.yuzu_emu.YuzuApplication; import org.yuzu.yuzu_emu.R; import org.yuzu.yuzu_emu.adapters.GameAdapter; @@ -43,19 +45,34 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam @Override public void onViewCreated(View view, Bundle savedInstanceState) { - int columns = getResources().getInteger(R.integer.game_grid_columns); - RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns); mAdapter = new GameAdapter(); - mRecyclerView.setLayoutManager(layoutManager); - mRecyclerView.setAdapter(mAdapter); - mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(ContextCompat.getDrawable(getActivity(), R.drawable.gamelist_divider), 1)); + // Organize our grid layout based on the current view. + if (isAdded()) { + view.getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (view.getMeasuredWidth() == 0) { + return; + } + + int columns = view.getMeasuredWidth() / + requireContext().getResources().getDimensionPixelSize(R.dimen.card_width); + if (columns == 0) { + columns = 1; + } + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), columns); + mRecyclerView.setLayoutManager(layoutManager); + mRecyclerView.setAdapter(mAdapter); + } + }); + } // Add swipe down to refresh gesture - final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.refresh_grid_games); + final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.swipe_refresh); pullToRefresh.setOnRefreshListener(() -> { - GameDatabase databaseHelper = YuzuApplication.databaseHelper; - databaseHelper.scanLibrary(databaseHelper.getWritableDatabase()); refresh(); pullToRefresh.setRefreshing(false); }); @@ -63,6 +80,8 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam @Override public void refresh() { + GameDatabase databaseHelper = YuzuApplication.databaseHelper; + databaseHelper.scanLibrary(databaseHelper.getWritableDatabase()); mPresenter.refresh(); updateTextView(); } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java index b75dc9a62..fd43575de 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java @@ -1,6 +1,7 @@ package org.yuzu.yuzu_emu.utils; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import com.squareup.picasso.Picasso; import com.squareup.picasso.Request; @@ -13,15 +14,16 @@ import java.nio.IntBuffer; public class GameIconRequestHandler extends RequestHandler { @Override public boolean canHandleRequest(Request data) { - return "iso".equals(data.uri.getScheme()); + return "content".equals(data.uri.getScheme()); } @Override public Result load(Request request, int networkPolicy) { - String url = request.uri.getHost() + request.uri.getPath(); - int[] vector = NativeLibrary.GetIcon(url); - Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565); - bitmap.copyPixelsFromBuffer(IntBuffer.wrap(vector)); + String gamePath = request.uri.toString(); + byte[] data = NativeLibrary.GetIcon(gamePath); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inMutable = true; + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); return new Result(bitmap, Picasso.LoadedFrom.DISK); } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java index 5033691b3..504dc5b6d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java @@ -31,7 +31,7 @@ public class PicassoUtils { public static void loadGameIcon(ImageView imageView, String gamePath) { Picasso .get() - .load(Uri.parse("iso:/" + gamePath)) + .load(Uri.parse(gamePath)) .fit() .centerInside() .config(Bitmap.Config.RGB_565) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java index 2dc0f34f3..41b8c6a27 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java @@ -16,8 +16,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder { private View itemView; public ImageView imageIcon; public TextView textGameTitle; - public TextView textCompany; - public TextView textFileName; + public TextView textGameCaption; public String gameId; @@ -36,8 +35,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder { imageIcon = itemView.findViewById(R.id.image_game_screen); textGameTitle = itemView.findViewById(R.id.text_game_title); - textCompany = itemView.findViewById(R.id.text_company); - textFileName = itemView.findViewById(R.id.text_filename); + textGameCaption = itemView.findViewById(R.id.text_game_caption); } public View getItemView() { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 6d1e75c40..2bd908308 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -24,6 +24,7 @@ #include "core/file_sys/vfs_real.h" #include "core/hid/hid_core.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" #include "core/perf_stats.h" #include "jni/config.h" #include "jni/emu_window/emu_window.h" @@ -34,7 +35,11 @@ namespace { class EmulationSession final { public: - EmulationSession() = default; + EmulationSession() { + m_system.Initialize(); + m_vfs = std::make_shared(); + } + ~EmulationSession() = default; static EmulationSession& GetInstance() { @@ -42,151 +47,205 @@ public: } const Core::System& System() const { - return system; + return m_system; } Core::System& System() { - return system; + return m_system; } const EmuWindow_Android& Window() const { - return *window; + return *m_window; } EmuWindow_Android& Window() { - return *window; + return *m_window; } ANativeWindow* NativeWindow() const { - return native_window; + return m_native_window; } - void SetNativeWindow(ANativeWindow* native_window_) { - native_window = native_window_; + void SetNativeWindow(ANativeWindow* m_native_window_) { + m_native_window = m_native_window_; } bool IsRunning() const { - std::scoped_lock lock(mutex); - return is_running; + std::scoped_lock lock(m_mutex); + return m_is_running; } const Core::PerfStatsResults& PerfStats() const { - std::scoped_lock perf_stats_lock(perf_stats_mutex); - return perf_stats; + std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); + return m_perf_stats; } void SurfaceChanged() { if (!IsRunning()) { return; } - window->OnSurfaceChanged(native_window); + m_window->OnSurfaceChanged(m_native_window); } Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { - std::scoped_lock lock(mutex); + std::scoped_lock lock(m_mutex); // Loads the configuration. Config{}; // Create the render window. - window = std::make_unique(&input_subsystem, native_window); + m_window = std::make_unique(&m_input_subsystem, m_native_window); // Initialize system. - system.SetShuttingDown(false); - system.Initialize(); - system.ApplySettings(); - system.HIDCore().ReloadInputDevices(); - system.SetContentProvider(std::make_unique()); - system.SetFilesystem(std::make_shared()); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); + m_system.SetShuttingDown(false); + m_system.Initialize(); + m_system.ApplySettings(); + m_system.HIDCore().ReloadInputDevices(); + m_system.SetContentProvider(std::make_unique()); + m_system.SetFilesystem(std::make_shared()); + m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem()); // Load the ROM. - load_result = system.Load(EmulationSession::GetInstance().Window(), filepath); - if (load_result != Core::SystemResultStatus::Success) { - return load_result; + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); + if (m_load_result != Core::SystemResultStatus::Success) { + return m_load_result; } // Complete initialization. - system.GPU().Start(); - system.GetCpuManager().OnGpuReady(); - system.RegisterExitCallback([&] { HaltEmulation(); }); + m_system.GPU().Start(); + m_system.GetCpuManager().OnGpuReady(); + m_system.RegisterExitCallback([&] { HaltEmulation(); }); return Core::SystemResultStatus::Success; } void ShutdownEmulation() { - std::scoped_lock lock(mutex); + std::scoped_lock lock(m_mutex); - is_running = false; + m_is_running = false; // Unload user input. - system.HIDCore().UnloadInputDevices(); + m_system.HIDCore().UnloadInputDevices(); // Shutdown the main emulated process - if (load_result == Core::SystemResultStatus::Success) { - system.DetachDebugger(); - system.ShutdownMainProcess(); - detached_tasks.WaitForAllTasks(); - load_result = Core::SystemResultStatus::ErrorNotInitialized; + if (m_load_result == Core::SystemResultStatus::Success) { + m_system.DetachDebugger(); + m_system.ShutdownMainProcess(); + m_detached_tasks.WaitForAllTasks(); + m_load_result = Core::SystemResultStatus::ErrorNotInitialized; } // Tear down the render window. - window.reset(); + m_window.reset(); + } + + void PauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Pause(); + } + + void UnPauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Run(); } void HaltEmulation() { - std::scoped_lock lock(mutex); - is_running = false; - cv.notify_one(); + std::scoped_lock lock(m_mutex); + m_is_running = false; + m_cv.notify_one(); } void RunEmulation() { { - std::scoped_lock lock(mutex); - is_running = true; + std::scoped_lock lock(m_mutex); + m_is_running = true; } - void(system.Run()); + void(m_system.Run()); - if (system.DebuggerEnabled()) { - system.InitializeDebugger(); + if (m_system.DebuggerEnabled()) { + m_system.InitializeDebugger(); } while (true) { { - std::unique_lock lock(mutex); - if (cv.wait_for(lock, std::chrono::milliseconds(100), - [&]() { return !is_running; })) { + std::unique_lock lock(m_mutex); + if (m_cv.wait_for(lock, std::chrono::milliseconds(100), + [&]() { return !m_is_running; })) { // Emulation halted. break; } } { // Refresh performance stats. - std::scoped_lock perf_stats_lock(perf_stats_mutex); - perf_stats = system.GetAndResetPerfStats(); + std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); + m_perf_stats = m_system.GetAndResetPerfStats(); } } } + std::string GetRomTitle(const std::string& path) { + return GetRomMetadata(path).title; + } + + std::vector GetRomIcon(const std::string& path) { + return GetRomMetadata(path).icon; + } + + void ResetRomMetadata() { + m_rom_metadata_cache.clear(); + } + +private: + struct RomMetadata { + std::string title; + std::vector icon; + }; + + RomMetadata GetRomMetadata(const std::string& path) { + if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { + return search->second; + } + + return CacheRomMetadata(path); + } + + RomMetadata CacheRomMetadata(const std::string& path) { + const auto file = Core::GetGameFileFromPath(m_vfs, path); + const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + + RomMetadata entry; + loader->ReadTitle(entry.title); + loader->ReadIcon(entry.icon); + + m_rom_metadata_cache[path] = entry; + + return entry; + } + private: static EmulationSession s_instance; - ANativeWindow* native_window{}; + // Frontend management + std::unordered_map m_rom_metadata_cache; - InputCommon::InputSubsystem input_subsystem; - Common::DetachedTasks detached_tasks; - Core::System system; + // Window management + std::unique_ptr m_window; + ANativeWindow* m_native_window{}; - Core::PerfStatsResults perf_stats{}; + // Core emulation + Core::System m_system; + InputCommon::InputSubsystem m_input_subsystem; + Common::DetachedTasks m_detached_tasks; + Core::PerfStatsResults m_perf_stats{}; + std::shared_ptr m_vfs; + Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; + bool m_is_running{}; - std::unique_ptr window; - std::condition_variable_any cv; - bool is_running{}; - Core::SystemResultStatus load_result{Core::SystemResultStatus::ErrorNotInitialized}; - - mutable std::mutex perf_stats_mutex; - mutable std::mutex mutex; + // Synchronization + std::condition_variable_any m_cv; + mutable std::mutex m_perf_stats_mutex; + mutable std::mutex m_mutex; }; /*static*/ EmulationSession EmulationSession::s_instance; @@ -275,16 +334,25 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env, } void Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation([[maybe_unused]] JNIEnv* env, - [[maybe_unused]] jclass clazz) {} + [[maybe_unused]] jclass clazz) { + EmulationSession::GetInstance().UnPauseEmulation(); +} void Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation([[maybe_unused]] JNIEnv* env, - [[maybe_unused]] jclass clazz) {} + [[maybe_unused]] jclass clazz) { + EmulationSession::GetInstance().PauseEmulation(); +} void Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) { EmulationSession::GetInstance().HaltEmulation(); } +void Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz) { + EmulationSession::GetInstance().ResetRomMetadata(); +} + jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz) { return static_cast(EmulationSession::GetInstance().IsRunning()); @@ -347,16 +415,21 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* } } -jintArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env, - [[maybe_unused]] jclass clazz, - [[maybe_unused]] jstring j_file) { - return {}; +jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz, + [[maybe_unused]] jstring j_filename) { + auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename)); + jbyteArray icon = env->NewByteArray(static_cast(icon_data.size())); + env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), + reinterpret_cast(icon_data.data())); + return icon; } jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_filename) { - return env->NewStringUTF(""); + auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename)); + return env->NewStringUTF(title.c_str()); } jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription([[maybe_unused]] JNIEnv* env, diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 210976201..bbc783aa8 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -19,6 +19,9 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIE JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env, jclass clazz); +JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env, + jclass clazz); + JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env, jclass clazz); @@ -39,8 +42,9 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JN JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jfloat x, jfloat y); -JNIEXPORT jintArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env, jclass clazz, - jstring j_file); +JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env, + jclass clazz, + jstring j_file); JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz, jstring j_filename); diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml index 217f02d34..a0d453719 100644 --- a/src/android/app/src/main/res/layout/card_game.xml +++ b/src/android/app/src/main/res/layout/card_game.xml @@ -1,81 +1,76 @@ - + android:paddingStart="4dp" + android:paddingTop="8dp" + android:paddingEnd="4dp" + android:paddingBottom="8dp" + android:transitionName="card_game"> - + + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" /> + android:gravity="center|top" + android:maxLines="2" + android:paddingLeft="2dp" + android:paddingRight="2dp" + android:paddingTop="8dp" + android:visibility="visible" + tools:text="The Legend of Zelda: The Wind Waker" /> - + - + - + - + diff --git a/src/android/app/src/main/res/layout/fragment_grid.xml b/src/android/app/src/main/res/layout/fragment_grid.xml index f5b6c2e19..01399e18d 100644 --- a/src/android/app/src/main/res/layout/fragment_grid.xml +++ b/src/android/app/src/main/res/layout/fragment_grid.xml @@ -1,12 +1,12 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> - + - \ No newline at end of file + diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index a3bb0c2c5..0b028a167 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml @@ -6,7 +6,7 @@ 64dp 72dp 256dp - 135dp + 150dp 20dp 3dp