mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 22:05:38 +00:00
Android: Multiple changes
- Fix game list display of NTSC-J region - Hook up quick load/save/reset options in emulation view. - Add speed limiter toggle to emulation view. - Add game list scanning options to main menu. - Add resume button (not yet hooked up to save states, it'll start the BIOS shell)
This commit is contained in:
parent
8665a24eee
commit
a7e24da7fe
|
@ -130,6 +130,7 @@ void AndroidHostInterface::SetUserDirectory()
|
||||||
void AndroidHostInterface::LoadSettings()
|
void AndroidHostInterface::LoadSettings()
|
||||||
{
|
{
|
||||||
CommonHostInterface::LoadSettings(m_settings_interface);
|
CommonHostInterface::LoadSettings(m_settings_interface);
|
||||||
|
CommonHostInterface::UpdateInputMap(m_settings_interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::UpdateInputMap()
|
void AndroidHostInterface::UpdateInputMap()
|
||||||
|
@ -396,6 +397,19 @@ void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database)
|
||||||
|
{
|
||||||
|
m_game_list->SetSearchDirectoriesFromSettings(m_settings_interface);
|
||||||
|
m_game_list->Refresh(invalidate_cache, invalidate_database);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::ApplySettings()
|
||||||
|
{
|
||||||
|
Settings old_settings = std::move(m_settings);
|
||||||
|
CommonHostInterface::LoadSettings(m_settings_interface);
|
||||||
|
CheckForSettingsChanges(old_settings);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
{
|
{
|
||||||
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
||||||
|
@ -530,28 +544,18 @@ DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobje
|
||||||
return code.value_or(-1);
|
return code.value_or(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_cache_path, jstring j_redump_dat_path,
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache, jboolean invalidate_database)
|
||||||
jarray j_search_directories, jboolean search_recursively)
|
|
||||||
{
|
{
|
||||||
// const std::string cache_path = AndroidHelpers::JStringToString(env, j_cache_path);
|
AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database);
|
||||||
std::string redump_dat_path = AndroidHelpers::JStringToString(env, j_redump_dat_path);
|
}
|
||||||
|
|
||||||
// TODO: This should use the base HostInterface.
|
static const char* DiscRegionToString(DiscRegion region) {
|
||||||
GameList gl;
|
static std::array<const char*, 4> names = {{"NTSC_J", "NTSC_U", "PAL", "Other"}};
|
||||||
if (!redump_dat_path.empty())
|
return names[static_cast<int>(region)];
|
||||||
gl.SetDatabaseFilename(std::move(redump_dat_path));
|
}
|
||||||
|
|
||||||
const jsize search_directories_size = env->GetArrayLength(j_search_directories);
|
|
||||||
for (jsize i = 0; i < search_directories_size; i++)
|
|
||||||
{
|
|
||||||
jobject search_dir_obj = env->GetObjectArrayElement(reinterpret_cast<jobjectArray>(j_search_directories), i);
|
|
||||||
const std::string search_dir = AndroidHelpers::JStringToString(env, reinterpret_cast<jstring>(search_dir_obj));
|
|
||||||
if (!search_dir.empty())
|
|
||||||
gl.AddDirectory(search_dir.c_str(), search_recursively);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.Refresh(false, false, nullptr);
|
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jarray, AndroidHostInterface_getGameListEntries, jobject obj)
|
||||||
|
{
|
||||||
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry");
|
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry");
|
||||||
Assert(entry_class != nullptr);
|
Assert(entry_class != nullptr);
|
||||||
|
|
||||||
|
@ -560,11 +564,12 @@ DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_ca
|
||||||
"String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
"String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||||
Assert(entry_constructor != nullptr);
|
Assert(entry_constructor != nullptr);
|
||||||
|
|
||||||
jobjectArray entry_array = env->NewObjectArray(gl.GetEntryCount(), entry_class, nullptr);
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
|
jobjectArray entry_array = env->NewObjectArray(hi->GetGameList()->GetEntryCount(), entry_class, nullptr);
|
||||||
Assert(entry_array != nullptr);
|
Assert(entry_array != nullptr);
|
||||||
|
|
||||||
u32 counter = 0;
|
u32 counter = 0;
|
||||||
for (const GameListEntry& entry : gl.GetEntries())
|
for (const GameListEntry& entry : hi->GetGameList()->GetEntries())
|
||||||
{
|
{
|
||||||
const Timestamp modified_ts(
|
const Timestamp modified_ts(
|
||||||
Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time)));
|
Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time)));
|
||||||
|
@ -572,7 +577,7 @@ DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_ca
|
||||||
jstring path = env->NewStringUTF(entry.path.c_str());
|
jstring path = env->NewStringUTF(entry.path.c_str());
|
||||||
jstring code = env->NewStringUTF(entry.code.c_str());
|
jstring code = env->NewStringUTF(entry.code.c_str());
|
||||||
jstring title = env->NewStringUTF(entry.title.c_str());
|
jstring title = env->NewStringUTF(entry.title.c_str());
|
||||||
jstring region = env->NewStringUTF(Settings::GetDiscRegionName(entry.region));
|
jstring region = env->NewStringUTF(DiscRegionToString(entry.region));
|
||||||
jstring type = env->NewStringUTF(GameList::EntryTypeToString(entry.type));
|
jstring type = env->NewStringUTF(GameList::EntryTypeToString(entry.type));
|
||||||
jstring compatibility_rating =
|
jstring compatibility_rating =
|
||||||
env->NewStringUTF(GameList::EntryCompatibilityRatingToString(entry.compatibility_rating));
|
env->NewStringUTF(GameList::EntryCompatibilityRatingToString(entry.compatibility_rating));
|
||||||
|
@ -587,3 +592,42 @@ DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_ca
|
||||||
|
|
||||||
return entry_array;
|
return entry_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj)
|
||||||
|
{
|
||||||
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
|
if (hi->IsEmulationThreadRunning())
|
||||||
|
{
|
||||||
|
hi->RunOnEmulationThread([hi]() {
|
||||||
|
hi->ApplySettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hi->ApplySettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_resetSystem, jobject obj, jboolean global, jint slot)
|
||||||
|
{
|
||||||
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
|
hi->RunOnEmulationThread([hi]() {
|
||||||
|
hi->ResetSystem();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_loadState, jobject obj, jboolean global, jint slot)
|
||||||
|
{
|
||||||
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
|
hi->RunOnEmulationThread([hi, global, slot]() {
|
||||||
|
hi->LoadState(global, slot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveState, jobject obj, jboolean global, jint slot)
|
||||||
|
{
|
||||||
|
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||||
|
hi->RunOnEmulationThread([hi, global, slot]() {
|
||||||
|
hi->SaveState(global, slot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,9 @@ public:
|
||||||
void SetControllerType(u32 index, std::string_view type_name);
|
void SetControllerType(u32 index, std::string_view type_name);
|
||||||
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
|
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
|
||||||
|
|
||||||
|
void RefreshGameList(bool invalidate_cache, bool invalidate_database);
|
||||||
|
void ApplySettings();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SetUserDirectory() override;
|
void SetUserDirectory() override;
|
||||||
void LoadSettings() override;
|
void LoadSettings() override;
|
||||||
|
|
|
@ -141,7 +141,7 @@ void AndroidSettingsInterface::DeleteValue(const char* section, const char* key)
|
||||||
std::vector<std::string> AndroidSettingsInterface::GetStringList(const char* section, const char* key)
|
std::vector<std::string> AndroidSettingsInterface::GetStringList(const char* section, const char* key)
|
||||||
{
|
{
|
||||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||||
jobject values_set = env->CallObjectMethod(m_java_shared_preferences, m_get_string_set, nullptr);
|
jobject values_set = env->CallObjectMethod(m_java_shared_preferences, m_get_string_set, env->NewStringUTF(GetSettingKey(section, key)), nullptr);
|
||||||
if (!values_set)
|
if (!values_set)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,6 @@ public class AndroidHostInterface
|
||||||
{
|
{
|
||||||
private long nativePointer;
|
private long nativePointer;
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("duckstation-native");
|
|
||||||
}
|
|
||||||
|
|
||||||
static public native AndroidHostInterface create(Context context);
|
static public native AndroidHostInterface create(Context context);
|
||||||
|
|
||||||
public AndroidHostInterface(long nativePointer)
|
public AndroidHostInterface(long nativePointer)
|
||||||
|
@ -28,4 +24,26 @@ public class AndroidHostInterface
|
||||||
public native void setControllerType(int index, String typeName);
|
public native void setControllerType(int index, String typeName);
|
||||||
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
||||||
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
||||||
|
|
||||||
|
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase);
|
||||||
|
public native GameListEntry[] getGameListEntries();
|
||||||
|
|
||||||
|
public native void resetSystem();
|
||||||
|
public native void loadState(boolean global, int slot);
|
||||||
|
public native void saveState(boolean global, int slot);
|
||||||
|
public native void applySettings();
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("duckstation-native");
|
||||||
|
}
|
||||||
|
|
||||||
|
static private AndroidHostInterface mInstance;
|
||||||
|
static public boolean createInstance(Context context) {
|
||||||
|
mInstance = create(context);
|
||||||
|
return mInstance != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public AndroidHostInterface getInstance() {
|
||||||
|
return mInstance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
public enum DiscRegion {
|
||||||
|
NTSC_J,
|
||||||
|
NTSC_U,
|
||||||
|
PAL,
|
||||||
|
Other
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -18,6 +19,7 @@ import android.view.MenuItem;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.core.app.NavUtils;
|
import androidx.core.app.NavUtils;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An example full-screen activity that shows and hides the system UI (i.e.
|
* An example full-screen activity that shows and hides the system UI (i.e.
|
||||||
|
@ -25,9 +27,17 @@ import androidx.core.app.NavUtils;
|
||||||
*/
|
*/
|
||||||
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
||||||
/**
|
/**
|
||||||
* Interface to the native emulator core
|
* Settings interfaces.
|
||||||
*/
|
*/
|
||||||
AndroidHostInterface mHostInterface;
|
SharedPreferences mPreferences;
|
||||||
|
private boolean getBooleanSetting(String key, boolean defaultValue) {
|
||||||
|
return mPreferences.getBoolean(key, defaultValue);
|
||||||
|
}
|
||||||
|
private void setBooleanSetting(String key, boolean value) {
|
||||||
|
SharedPreferences.Editor editor = mPreferences.edit();
|
||||||
|
editor.putBoolean(key, value);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Touchscreen controller overlay
|
* Touchscreen controller overlay
|
||||||
|
@ -96,15 +106,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
@Override
|
@Override
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
// Once we get a surface, we can boot.
|
// Once we get a surface, we can boot.
|
||||||
if (mHostInterface.isEmulationThreadRunning()) {
|
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) {
|
||||||
mHostInterface.surfaceChanged(holder.getSurface(), format, width, height);
|
AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String bootPath = getIntent().getStringExtra("bootPath");
|
String bootPath = getIntent().getStringExtra("bootPath");
|
||||||
String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath");
|
String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath");
|
||||||
|
boolean resumeState = getIntent().getBooleanExtra("resumeState", false);
|
||||||
|
|
||||||
if (!mHostInterface
|
if (!AndroidHostInterface.getInstance()
|
||||||
.startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) {
|
.startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) {
|
||||||
Log.e("EmulationActivity", "Failed to start emulation thread");
|
Log.e("EmulationActivity", "Failed to start emulation thread");
|
||||||
finishActivity(0);
|
finishActivity(0);
|
||||||
|
@ -114,16 +125,17 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
if (!mHostInterface.isEmulationThreadRunning())
|
if (!AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Log.i("EmulationActivity", "Stopping emulation thread");
|
Log.i("EmulationActivity", "Stopping emulation thread");
|
||||||
mHostInterface.stopEmulationThread();
|
AndroidHostInterface.getInstance().stopEmulationThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
setContentView(R.layout.activity_emulation);
|
setContentView(R.layout.activity_emulation);
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
@ -142,19 +154,15 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mHostInterface = AndroidHostInterface.create(this);
|
|
||||||
if (mHostInterface == null)
|
|
||||||
throw new InstantiationError("Failed to create host interface");
|
|
||||||
|
|
||||||
// Create touchscreen controller.
|
// Create touchscreen controller.
|
||||||
FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
||||||
mTouchscreenController = new TouchscreenControllerView(this);
|
mTouchscreenController = new TouchscreenControllerView(this);
|
||||||
activityLayout.addView(mTouchscreenController);
|
activityLayout.addView(mTouchscreenController);
|
||||||
mTouchscreenController.init(0, "DigitalController", mHostInterface);
|
mTouchscreenController.init(0, "DigitalController", AndroidHostInterface.getInstance());
|
||||||
setTouchscreenControllerVisibility(true);
|
setTouchscreenControllerVisibility(true);
|
||||||
|
|
||||||
// Hook up controller input.
|
// Hook up controller input.
|
||||||
mContentView.initControllerKeyMapping(mHostInterface, "DigitalController");
|
mContentView.initControllerKeyMapping("DigitalController");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -172,6 +180,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
getMenuInflater().inflate(R.menu.menu_emulation, menu);
|
getMenuInflater().inflate(R.menu.menu_emulation, menu);
|
||||||
menu.findItem(R.id.show_controller).setChecked(mTouchscreenControllerVisible);
|
menu.findItem(R.id.show_controller).setChecked(mTouchscreenControllerVisible);
|
||||||
|
menu.findItem(R.id.enable_speed_limiter).setChecked(getBooleanSetting("Main/SpeedLimiterEnabled", true));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,6 +200,18 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||||
setTouchscreenControllerVisibility(!mTouchscreenControllerVisible);
|
setTouchscreenControllerVisibility(!mTouchscreenControllerVisible);
|
||||||
item.setChecked(mTouchscreenControllerVisible);
|
item.setChecked(mTouchscreenControllerVisible);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (id == R.id.enable_speed_limiter) {
|
||||||
|
boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true);
|
||||||
|
setBooleanSetting("Main/SpeedLimiterEnabled", newSetting);
|
||||||
|
item.setChecked(newSetting);
|
||||||
|
AndroidHostInterface.getInstance().applySettings();
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.reset) {
|
||||||
|
AndroidHostInterface.getInstance().resetSystem();
|
||||||
|
} else if (id == R.id.quick_load) {
|
||||||
|
AndroidHostInterface.getInstance().loadState(false, 0);
|
||||||
|
} else if (id == R.id.quick_save) {
|
||||||
|
AndroidHostInterface.getInstance().saveState(false, 0);
|
||||||
} else if (id == R.id.quit) {
|
} else if (id == R.id.quit) {
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -48,7 +48,6 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AndroidHostInterface mHostInterface;
|
|
||||||
private ArrayMap<Integer, Integer> mControllerKeyMapping;
|
private ArrayMap<Integer, Integer> mControllerKeyMapping;
|
||||||
|
|
||||||
private void addControllerKeyMapping(int keyCode, String controllerType, String buttonName) {
|
private void addControllerKeyMapping(int keyCode, String controllerType, String buttonName) {
|
||||||
|
@ -59,9 +58,7 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
mControllerKeyMapping.put(keyCode, mapping);
|
mControllerKeyMapping.put(keyCode, mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initControllerKeyMapping(AndroidHostInterface hostInterface,
|
public void initControllerKeyMapping(String controllerType) {
|
||||||
String controllerType) {
|
|
||||||
mHostInterface = hostInterface;
|
|
||||||
mControllerKeyMapping = new ArrayMap<>();
|
mControllerKeyMapping = new ArrayMap<>();
|
||||||
|
|
||||||
// TODO: Don't hardcode...
|
// TODO: Don't hardcode...
|
||||||
|
@ -86,7 +83,7 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
final int mapping = mControllerKeyMapping.get(keyCode);
|
final int mapping = mControllerKeyMapping.get(keyCode);
|
||||||
mHostInterface.setControllerButtonState(0, mapping, pressed);
|
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping, pressed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,44 +15,21 @@ import androidx.preference.PreferenceManager;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class GameList {
|
public class GameList {
|
||||||
static {
|
|
||||||
System.loadLibrary("duckstation-native");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private String mCachePath;
|
|
||||||
private String mRedumpDatPath;
|
|
||||||
private String[] mSearchDirectories;
|
|
||||||
private boolean mSearchRecursively;
|
|
||||||
private GameListEntry[] mEntries;
|
private GameListEntry[] mEntries;
|
||||||
|
private ListViewAdapter mAdapter;
|
||||||
static private native GameListEntry[] getEntries(String cachePath, String redumpDatPath,
|
|
||||||
String[] searchDirectories,
|
|
||||||
boolean searchRecursively);
|
|
||||||
|
|
||||||
public GameList(Context context) {
|
public GameList(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
refresh();
|
mAdapter = new ListViewAdapter();
|
||||||
|
mEntries = new GameListEntry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh(boolean invalidateCache, boolean invalidateDatabase) {
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
|
||||||
mCachePath = preferences.getString("GameList/CachePath", "");
|
|
||||||
mRedumpDatPath = preferences.getString("GameList/RedumpDatPath", "");
|
|
||||||
|
|
||||||
Set<String> searchDirectories =
|
|
||||||
preferences.getStringSet("GameList/SearchDirectories", null);
|
|
||||||
if (searchDirectories != null) {
|
|
||||||
mSearchDirectories = new String[searchDirectories.size()];
|
|
||||||
searchDirectories.toArray(mSearchDirectories);
|
|
||||||
} else {
|
|
||||||
mSearchDirectories = new String[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
mSearchRecursively = preferences.getBoolean("GameList/SearchRecursively", true);
|
|
||||||
|
|
||||||
// Search and get entries from native code
|
// Search and get entries from native code
|
||||||
mEntries = getEntries(mCachePath, mRedumpDatPath, mSearchDirectories, mSearchRecursively);
|
AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase);
|
||||||
|
mEntries = AndroidHostInterface.getInstance().getGameListEntries();
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getEntryCount() {
|
public int getEntryCount() {
|
||||||
|
@ -97,6 +74,6 @@ public class GameList {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseAdapter getListViewAdapter() {
|
public BaseAdapter getListViewAdapter() {
|
||||||
return new ListViewAdapter();
|
return mAdapter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class GameListEntry {
|
||||||
private String mTitle;
|
private String mTitle;
|
||||||
private long mSize;
|
private long mSize;
|
||||||
private String mModifiedTime;
|
private String mModifiedTime;
|
||||||
private ConsoleRegion mRegion;
|
private DiscRegion mRegion;
|
||||||
private EntryType mType;
|
private EntryType mType;
|
||||||
private CompatibilityRating mCompatibilityRating;
|
private CompatibilityRating mCompatibilityRating;
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ public class GameListEntry {
|
||||||
mModifiedTime = modifiedTime;
|
mModifiedTime = modifiedTime;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mRegion = ConsoleRegion.valueOf(region);
|
mRegion = DiscRegion.valueOf(region);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
mRegion = ConsoleRegion.NTSC_U;
|
mRegion = DiscRegion.NTSC_U;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -74,7 +74,7 @@ public class GameListEntry {
|
||||||
|
|
||||||
public String getModifiedTime() { return mModifiedTime; }
|
public String getModifiedTime() { return mModifiedTime; }
|
||||||
|
|
||||||
public ConsoleRegion getRegion() {
|
public DiscRegion getRegion() {
|
||||||
return mRegion;
|
return mRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,22 +43,26 @@ public class MainActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (!AndroidHostInterface.createInstance(this)) {
|
||||||
|
Log.i("MainActivity", "Failed to create host interface");
|
||||||
|
throw new RuntimeException("Failed to create host interface");
|
||||||
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
FloatingActionButton fab = findViewById(R.id.fab);
|
findViewById(R.id.fab_add_game_directory).setOnClickListener(new View.OnClickListener() {
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
if (!checkForExternalStoragePermissions())
|
startAddGameDirectory();
|
||||||
return;
|
}
|
||||||
|
});
|
||||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
findViewById(R.id.fab_resume).setOnClickListener(new View.OnClickListener() {
|
||||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
@Override
|
||||||
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
public void onClick(View view) {
|
||||||
startActivityForResult(Intent.createChooser(i, "Choose directory"),
|
startEmulation(null, true);
|
||||||
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -69,7 +73,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
startEmulation(mGameList.getEntry(position).getPath());
|
startEmulation(mGameList.getEntry(position).getPath(), true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||||
|
@ -89,6 +93,18 @@ public class MainActivity extends AppCompatActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mGameList.refresh(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startAddGameDirectory() {
|
||||||
|
if (!checkForExternalStoragePermissions())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||||
|
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
||||||
|
startActivityForResult(Intent.createChooser(i, "Choose directory"),
|
||||||
|
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,6 +122,13 @@ public class MainActivity extends AppCompatActivity {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
|
||||||
//noinspection SimplifiableIfStatement
|
//noinspection SimplifiableIfStatement
|
||||||
|
if (id == R.id.action_add_game_directory) {
|
||||||
|
startAddGameDirectory();
|
||||||
|
} else if (id == R.id.action_scan_for_new_games) {
|
||||||
|
mGameList.refresh(false, false);
|
||||||
|
} if (id == R.id.action_rescan_all_games) {
|
||||||
|
mGameList.refresh(true, false);
|
||||||
|
}
|
||||||
if (id == R.id.action_settings) {
|
if (id == R.id.action_settings) {
|
||||||
Intent intent = new Intent(this, SettingsActivity.class);
|
Intent intent = new Intent(this, SettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
@ -132,16 +155,16 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
Set<String> currentValues = prefs.getStringSet("GameList/SearchDirectories", null);
|
Set<String> currentValues = prefs.getStringSet("GameList/RecursivePaths", null);
|
||||||
if (currentValues == null)
|
if (currentValues == null)
|
||||||
currentValues = new HashSet<String>();
|
currentValues = new HashSet<String>();
|
||||||
|
|
||||||
currentValues.add(path);
|
currentValues.add(path);
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
editor.putStringSet("GameList/SearchDirectories", currentValues);
|
editor.putStringSet("GameList/RecursivePaths", currentValues);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
Log.i("MainActivity", "Added path '" + path + "' to game list search directories");
|
Log.i("MainActivity", "Added path '" + path + "' to game list search directories");
|
||||||
mGameList.refresh();
|
mGameList.refresh(false, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -175,13 +198,14 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean startEmulation(String bootPath) {
|
private boolean startEmulation(String bootPath, boolean resumeState) {
|
||||||
if (!checkForExternalStoragePermissions()) {
|
if (!checkForExternalStoragePermissions()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = new Intent(this, EmulationActivity.class);
|
Intent intent = new Intent(this, EmulationActivity.class);
|
||||||
intent.putExtra("bootPath", bootPath);
|
intent.putExtra("bootPath", bootPath);
|
||||||
|
intent.putExtra("resumeState", resumeState);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M8,5v14l11,-7z"/>
|
||||||
|
</vector>
|
|
@ -24,7 +24,17 @@
|
||||||
<include layout="@layout/content_main"/>
|
<include layout="@layout/content_main"/>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab_resume"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginBottom="@dimen/fab_margin"
|
||||||
|
android:layout_marginRight="96dp"
|
||||||
|
app:backgroundTint="@android:color/background_light"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_play_arrow_24" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab_add_game_directory"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
|
|
|
@ -2,14 +2,22 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<group android:id="@+id/quick_load_save">
|
<group android:id="@+id/actions">
|
||||||
<item android:title="Quick Load" />
|
<item android:id="@+id/reset"
|
||||||
<item android:title="Quick Save" />
|
android:title="Reset" />
|
||||||
|
<item android:id="@+id/quick_load"
|
||||||
|
android:title="Quick Load" />
|
||||||
|
<item android:id="@+id/quick_save"
|
||||||
|
android:title="Quick Save" />
|
||||||
</group>
|
</group>
|
||||||
<group android:id="@+id/quick_settings">
|
<group android:id="@+id/quick_settings">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/change_disc"
|
android:id="@+id/change_disc"
|
||||||
android:title="Change Disc" />
|
android:title="Change Disc" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/enable_speed_limiter"
|
||||||
|
android:title="Enable Speed Limiter"
|
||||||
|
android:checkable="true" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/show_controller"
|
android:id="@+id/show_controller"
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:context="com.github.stenzek.duckstation.MainActivity" >
|
tools:context="com.github.stenzek.duckstation.MainActivity" >
|
||||||
|
<group android:id="@+id/game_list">
|
||||||
|
<item android:id="@+id/action_add_game_directory"
|
||||||
|
android:title="Add Game Directory" />
|
||||||
|
<item android:id="@+id/action_scan_for_new_games"
|
||||||
|
android:title="Scan For New Games" />
|
||||||
|
<item android:id="@+id/action_rescan_all_games"
|
||||||
|
android:title="Rescan All Games" />
|
||||||
|
</group>
|
||||||
<item android:id="@+id/action_settings"
|
<item android:id="@+id/action_settings"
|
||||||
android:title="@string/action_settings"
|
android:title="@string/action_settings"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
|
|
Loading…
Reference in a new issue