Просмотр исходного кода

android: frontend: Implement game grid view. (#9)

bunnei 3 лет назад
Родитель
Сommit
0e52d11ede

+ 1 - 1
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

+ 7 - 2
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).
      */

+ 2 - 6
src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java

@@ -86,11 +86,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> 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<GameViewHolder> 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();

+ 1 - 1
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() {

+ 4 - 1
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()) {

+ 2 - 0
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();
         }
     }

+ 27 - 8
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();
     }

+ 7 - 5
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);
     }
 }

+ 1 - 1
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)

+ 2 - 4
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() {

+ 142 - 69
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<FileSys::RealVfsFilesystem>();
+    }
+
     ~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<EmuWindow_Android>(&input_subsystem, native_window);
+        m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window);
 
         // Initialize system.
-        system.SetShuttingDown(false);
-        system.Initialize();
-        system.ApplySettings();
-        system.HIDCore().ReloadInputDevices();
-        system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
-        system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
-        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<FileSys::ContentProviderUnion>());
+        m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
+        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<u8> GetRomIcon(const std::string& path) {
+        return GetRomMetadata(path).icon;
+    }
+
+    void ResetRomMetadata() {
+        m_rom_metadata_cache.clear();
+    }
+
 private:
-    static EmulationSession s_instance;
+    struct RomMetadata {
+        std::string title;
+        std::vector<u8> 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);
+    }
 
-    ANativeWindow* native_window{};
+    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);
 
-    InputCommon::InputSubsystem input_subsystem;
-    Common::DetachedTasks detached_tasks;
-    Core::System system;
+        RomMetadata entry;
+        loader->ReadTitle(entry.title);
+        loader->ReadIcon(entry.icon);
 
-    Core::PerfStatsResults perf_stats{};
+        m_rom_metadata_cache[path] = entry;
 
-    std::unique_ptr<EmuWindow_Android> window;
-    std::condition_variable_any cv;
-    bool is_running{};
-    Core::SystemResultStatus load_result{Core::SystemResultStatus::ErrorNotInitialized};
+        return entry;
+    }
+
+private:
+    static EmulationSession s_instance;
 
-    mutable std::mutex perf_stats_mutex;
-    mutable std::mutex mutex;
+    // Frontend management
+    std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
+
+    // Window management
+    std::unique_ptr<EmuWindow_Android> m_window;
+    ANativeWindow* m_native_window{};
+
+    // Core emulation
+    Core::System m_system;
+    InputCommon::InputSubsystem m_input_subsystem;
+    Common::DetachedTasks m_detached_tasks;
+    Core::PerfStatsResults m_perf_stats{};
+    std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs;
+    Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
+    bool m_is_running{};
+
+    // 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<jboolean>(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<jsize>(icon_data.size()));
+    env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
+                            reinterpret_cast<jbyte*>(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,

+ 6 - 2
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);

+ 57 - 62
src/android/app/src/main/res/layout/card_game.xml

@@ -1,81 +1,76 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="?attr/selectableItemBackground"
     android:clickable="true"
+    android:clipToPadding="false"
     android:focusable="true"
-    android:foreground="?android:attr/selectableItemBackground"
-    android:transitionName="card_game"
-    tools:layout_width="match_parent">
+    android:paddingStart="4dp"
+    android:paddingTop="8dp"
+    android:paddingEnd="4dp"
+    android:paddingBottom="8dp"
+    android:transitionName="card_game">
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/linearLayout"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="8dp">
+    <androidx.cardview.widget.CardView
+        android:id="@+id/card_game_art"
+        android:layout_width="150dp"
+        android:layout_height="150dp"
+        app:cardCornerRadius="4dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
 
         <ImageView
             android:id="@+id/image_game_screen"
-            android:layout_width="56dp"
-            android:layout_height="56dp"
-            android:adjustViewBounds="false"
-            android:cropToPadding="false"
-            android:scaleType="fitCenter"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:scaleType="fitCenter" />
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
 
         <TextView
-            android:id="@+id/text_game_title"
+            android:id="@+id/text_game_title_inner"
             style="@android:style/TextAppearance.Material.Subhead"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
-            android:baselineAligned="false"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
             android:ellipsize="end"
-            android:gravity="center_vertical"
-            android:lines="1"
-            android:maxLines="1"
-            android:textAlignment="viewStart"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@+id/image_game_screen"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:text="The Legend of Zelda\nOcarina of Time 3D"
-            android:textColor="@color/header_text" />
+            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" />
 
-        <TextView
-            android:id="@+id/text_company"
-            style="@android:style/TextAppearance.Material.Caption"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:lines="1"
-            android:maxLines="1"
-            app:layout_constraintBottom_toBottomOf="@+id/image_game_screen"
-            app:layout_constraintStart_toStartOf="@+id/text_game_title"
-            app:layout_constraintTop_toBottomOf="@+id/text_game_title"
-            app:layout_constraintVertical_bias="0.842"
-            tools:text="Nintendo"
-            android:textColor="@color/header_subtext" />
+    </androidx.cardview.widget.CardView>
 
-        <TextView
-            android:id="@+id/text_filename"
-            style="@android:style/TextAppearance.Material.Caption"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:lines="1"
-            android:maxLines="1"
-            app:layout_constraintBottom_toBottomOf="@+id/image_game_screen"
-            app:layout_constraintStart_toStartOf="@+id/text_game_title"
-            app:layout_constraintTop_toBottomOf="@+id/text_game_title"
-            app:layout_constraintVertical_bias="0.0"
-            tools:text="Pilotwings_Resort.cxi"
-            android:textColor="@color/header_subtext" />
+    <TextView
+        android:id="@+id/text_game_title"
+        style="@android:style/TextAppearance.Material.Subhead"
+        android:layout_width="150dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:maxLines="2"
+        android:paddingTop="8dp"
+        app:layout_constraintEnd_toEndOf="@+id/card_game_art"
+        app:layout_constraintStart_toStartOf="@+id/card_game_art"
+        app:layout_constraintTop_toBottomOf="@+id/card_game_art"
+        tools:text="The Legend of Zelda: The Wind Waker" />
 
-    </androidx.constraintlayout.widget.ConstraintLayout>
+    <TextView
+        android:id="@+id/text_game_caption"
+        style="@android:style/TextAppearance.Material.Caption"
+        android:layout_width="150dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:lines="1"
+        android:maxLines="1"
+        android:paddingTop="8dp"
+        app:layout_constraintEnd_toEndOf="@+id/card_game_art"
+        app:layout_constraintStart_toStartOf="@+id/card_game_art"
+        app:layout_constraintTop_toBottomOf="@+id/text_game_title"
+        tools:text="Nintendo" />
 
-</androidx.cardview.widget.CardView>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 12 - 11
src/android/app/src/main/res/layout/fragment_grid.xml

@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
+             xmlns:tools="http://schemas.android.com/tools"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
 
     <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-        android:id="@+id/refresh_grid_games"
-        android:layout_width="match_parent"
+        android:id="@+id/swipe_refresh"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content">
 
         <RelativeLayout
@@ -22,12 +22,13 @@
                 android:textSize="18sp"
                 android:gravity="center" />
 
-            <androidx.recyclerview.widget.RecyclerView
-                android:id="@+id/grid_games"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                tools:listitem="@layout/card_game" />
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/grid_games"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false"
+            tools:listitem="@layout/card_game" />
         </RelativeLayout>
 
     </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
-</FrameLayout>
+</FrameLayout>

+ 1 - 1
src/android/app/src/main/res/values/dimens.xml

@@ -6,7 +6,7 @@
     <dimen name="spacing_list">64dp</dimen>
     <dimen name="spacing_fab">72dp</dimen>
     <dimen name="menu_width">256dp</dimen>
-    <dimen name="card_width">135dp</dimen>
+    <dimen name="card_width">150dp</dimen>
 
     <dimen name="dialog_margin">20dp</dimen>
     <dimen name="elevated_app_bar">3dp</dimen>