diff options
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. @@ -205,6 +205,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).       */      public static native boolean IsRunning(); 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<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(); 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<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, 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 @@  <?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> 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 @@  <?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>
\ No newline at end of file +</FrameLayout> 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 @@      <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> | 
