diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 37a750962..7bfef59df 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/android/app/build.gradle b/android/app/build.gradle index 782b0d479..d0b279d7c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,6 +24,10 @@ android { version "3.10.2" } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { externalNativeBuild { cmake { diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index d13d6c205..682411a85 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -20,7 +20,14 @@ Log_SetChannel(AndroidHostInterface); static JavaVM* s_jvm; static jclass s_AndroidHostInterface_class; static jmethodID s_AndroidHostInterface_constructor; -static jfieldID s_AndroidHostInterface_field_nativePointer; +static jfieldID s_AndroidHostInterface_field_mNativePointer; +static jmethodID s_AndroidHostInterface_method_reportError; +static jmethodID s_AndroidHostInterface_method_reportMessage; +static jmethodID s_EmulationActivity_method_reportError; +static jmethodID s_EmulationActivity_method_reportMessage; +static jmethodID s_EmulationActivity_method_onEmulationStarted; +static jmethodID s_EmulationActivity_method_onEmulationStopped; +static jmethodID s_EmulationActivity_method_onGameTitleChanged; namespace AndroidHelpers { // helper for retrieving the current per-thread jni environment @@ -36,7 +43,7 @@ JNIEnv* GetJNIEnv() AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj) { return reinterpret_cast( - static_cast(env->GetLongField(obj, s_AndroidHostInterface_field_nativePointer))); + static_cast(env->GetLongField(obj, s_AndroidHostInterface_field_mNativePointer))); } std::string JStringToString(JNIEnv* env, jstring str) @@ -95,12 +102,26 @@ void AndroidHostInterface::RequestExit() void AndroidHostInterface::ReportError(const char* message) { - HostInterface::ReportError(message); + CommonHostInterface::ReportError(message); + + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jstring message_jstr = env->NewStringUTF(message); + if (m_emulation_activity_object) + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportError, message_jstr); + else + env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportError, message_jstr); } void AndroidHostInterface::ReportMessage(const char* message) { - HostInterface::ReportMessage(message); + CommonHostInterface::ReportMessage(message); + + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jstring message_jstr = env->NewStringUTF(message); + if (m_emulation_activity_object) + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportMessage, message_jstr); + else + env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportMessage, message_jstr); } std::string AndroidHostInterface::GetStringSettingValue(const char* section, const char* key, const char* default_value) @@ -141,22 +162,17 @@ void AndroidHostInterface::UpdateInputMap() CommonHostInterface::UpdateInputMap(m_settings_interface); } -bool AndroidHostInterface::StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params) +bool AndroidHostInterface::StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state) { Assert(!IsEmulationThreadRunning()); + emulation_activity = AndroidHelpers::GetJNIEnv()->NewGlobalRef(emulation_activity); + Log_DevPrintf("Starting emulation thread..."); m_emulation_thread_stop_request.store(false); - m_emulation_thread = - std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, initial_surface, std::move(boot_params)); - m_emulation_thread_started.Wait(); - if (!m_emulation_thread_start_result.load()) - { - m_emulation_thread.join(); - Log_ErrorPrint("Failed to start emulation in thread"); - return false; - } - + m_emulation_thread = std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, emulation_activity, + initial_surface, std::move(boot_params), resume_state); return true; } @@ -196,36 +212,48 @@ void AndroidHostInterface::RunOnEmulationThread(std::function function, m_callback_mutex.unlock(); } -void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params) +void AndroidHostInterface::EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state) { JNIEnv* thread_env; if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK) { - Log_ErrorPrintf("Failed to attach JNI to thread"); - m_emulation_thread_start_result.store(false); - m_emulation_thread_started.Signal(); + ReportError("Failed to attach JNI to thread"); return; } CreateImGuiContext(); m_surface = initial_surface; - ApplySettings(); + m_emulation_activity_object = emulation_activity; + ApplySettings(true); // Boot system. - if (!BootSystem(boot_params)) + bool boot_result = false; + if (resume_state) + { + if (boot_params.filename.empty()) + boot_result = ResumeSystemFromMostRecentState(); + else + boot_result = ResumeSystemFromState(boot_params.filename.c_str(), true); + } + else { - Log_ErrorPrintf("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str()); + boot_result = BootSystem(boot_params); + } + + if (!boot_result) + { + ReportFormattedError("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str()); DestroyImGuiContext(); - m_emulation_thread_start_result.store(false); - m_emulation_thread_started.Signal(); + thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); + thread_env->DeleteGlobalRef(m_emulation_activity_object); + m_emulation_activity_object = {}; s_jvm->DetachCurrentThread(); return; } // System is ready to go. - m_emulation_thread_start_result.store(true); - m_emulation_thread_started.Signal(); - + thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); while (!m_emulation_thread_stop_request.load()) { // run any events @@ -264,8 +292,11 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf } } - DestroySystem(); + thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); + PowerOffSystem(); DestroyImGuiContext(); + thread_env->DeleteGlobalRef(m_emulation_activity_object); + m_emulation_activity_object = {}; s_jvm->DetachCurrentThread(); } @@ -308,9 +339,27 @@ void AndroidHostInterface::ReleaseHostDisplay() m_display.reset(); } +void AndroidHostInterface::OnSystemDestroyed() +{ + CommonHostInterface::OnSystemDestroyed(); + ClearOSDMessages(); +} + +void AndroidHostInterface::OnRunningGameChanged() +{ + CommonHostInterface::OnRunningGameChanged(); + + if (m_emulation_activity_object) + { + JNIEnv* env = AndroidHelpers::GetJNIEnv(); + jstring title_string = env->NewStringUTF(System::GetRunningTitle().c_str()); + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onGameTitleChanged, title_string); + } +} + void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height) { - Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height); + ReportFormattedMessage("SurfaceChanged %p %d %d %d", surface, format, width, height); if (m_surface == surface) { if (m_display) @@ -412,10 +461,11 @@ void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidat m_game_list->Refresh(invalidate_cache, invalidate_database); } -void AndroidHostInterface::ApplySettings() +void AndroidHostInterface::ApplySettings(bool display_osd_messages) { Settings old_settings = std::move(g_settings); CommonHostInterface::LoadSettings(m_settings_interface); + CommonHostInterface::FixIncompatibleSettings(display_osd_messages); CheckForSettingsChanges(old_settings); } @@ -439,10 +489,26 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) return -1; } - if ((s_AndroidHostInterface_constructor = env->GetMethodID(s_AndroidHostInterface_class, "", "()V")) == - nullptr || - (s_AndroidHostInterface_field_nativePointer = - env->GetFieldID(s_AndroidHostInterface_class, "nativePointer", "J")) == nullptr) + jclass emulation_activity_class; + if ((s_AndroidHostInterface_constructor = + env->GetMethodID(s_AndroidHostInterface_class, "", "(Landroid/content/Context;)V")) == nullptr || + (s_AndroidHostInterface_field_mNativePointer = + env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr || + (s_AndroidHostInterface_method_reportError = + env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || + (s_AndroidHostInterface_method_reportMessage = + env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || + (emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr || + (s_EmulationActivity_method_reportError = + env->GetMethodID(emulation_activity_class, "reportError", "(Ljava/lang/String;)V")) == nullptr || + (s_EmulationActivity_method_reportMessage = + env->GetMethodID(emulation_activity_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr || + (s_EmulationActivity_method_onEmulationStarted = + env->GetMethodID(emulation_activity_class, "onEmulationStarted", "()V")) == nullptr || + (s_EmulationActivity_method_onEmulationStopped = + env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr || + (s_EmulationActivity_method_onGameTitleChanged = + env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr) { Log_ErrorPrint("AndroidHostInterface lookups failed"); return -1; @@ -457,12 +523,13 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) #define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \ extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env, __VA_ARGS__) -DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object, jstring user_directory) +DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object, + jstring user_directory) { Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG); // initialize the java side - jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor); + jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object); if (!java_obj) { Log_ErrorPrint("Failed to create Java AndroidHostInterface"); @@ -483,7 +550,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, job return nullptr; } - env->SetLongField(java_obj, s_AndroidHostInterface_field_nativePointer, + env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer, static_cast(reinterpret_cast(cpp_obj))); return java_obj; @@ -494,8 +561,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning, return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning(); } -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject surface, - jstring filename, jstring state_filename) +DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject emulationActivity, + jobject surface, jstring filename, jboolean resume_state, jstring state_filename) { ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface); if (!native_surface) @@ -509,7 +576,8 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobj SystemBootParameters boot_params; boot_params.filename = AndroidHelpers::JStringToString(env, filename); - return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(native_surface, std::move(boot_params)); + return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(emulationActivity, native_surface, + std::move(boot_params), resume_state); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThread, jobject obj) @@ -526,7 +594,8 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, j AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); hi->RunOnEmulationThread( - [hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true); + [hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, + false); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerType, jobject obj, jint index, jstring controller_type) @@ -629,11 +698,11 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); if (hi->IsEmulationThreadRunning()) { - hi->RunOnEmulationThread([hi]() { hi->ApplySettings(); }); + hi->RunOnEmulationThread([hi]() { hi->ApplySettings(false); }); } else { - hi->ApplySettings(); + hi->ApplySettings(false); } } diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index fa72ca339..5667631b4 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -35,7 +35,8 @@ public: float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); } - bool StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params); + bool StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state); void RunOnEmulationThread(std::function function, bool blocking = false); void StopEmulationThread(); @@ -46,7 +47,7 @@ public: void SetControllerAxisState(u32 index, s32 button_code, float value); void RefreshGameList(bool invalidate_cache, bool invalidate_database); - void ApplySettings(); + void ApplySettings(bool display_osd_messages); protected: void SetUserDirectory() override; @@ -56,13 +57,18 @@ protected: bool AcquireHostDisplay() override; void ReleaseHostDisplay() override; + void OnSystemDestroyed() override; + void OnRunningGameChanged() override; + private: - void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params); + void EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, + SystemBootParameters boot_params, bool resume_state); void CreateImGuiContext(); void DestroyImGuiContext(); jobject m_java_object = {}; + jobject m_emulation_activity_object = {}; AndroidSettingsInterface m_settings_interface; @@ -73,8 +79,6 @@ private: std::thread m_emulation_thread; std::atomic_bool m_emulation_thread_stop_request{false}; - std::atomic_bool m_emulation_thread_start_result{false}; - Common::Event m_emulation_thread_started; }; namespace AndroidHelpers { diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index 59231f01e..87313d2a5 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -4,37 +4,57 @@ import android.content.Context; import android.os.Environment; import android.util.Log; import android.view.Surface; +import android.widget.Toast; -public class AndroidHostInterface -{ - private long nativePointer; +import com.google.android.material.snackbar.Snackbar; + +public class AndroidHostInterface { + private long mNativePointer; + private Context mContext; static public native AndroidHostInterface create(Context context, String userDirectory); - public AndroidHostInterface(long nativePointer) - { - this.nativePointer = nativePointer; + public AndroidHostInterface(Context context) { + this.mContext = context; + } + + public void reportError(String message) { + Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); + } + + public void reportMessage(String message) { + Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); } public native boolean isEmulationThreadRunning(); - public native boolean startEmulationThread(Surface surface, String filename, String state_filename); + + public native boolean startEmulationThread(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String state_filename); + public native void stopEmulationThread(); public native void surfaceChanged(Surface surface, int format, int width, int height); // TODO: Find a better place for this. public native void setControllerType(int index, String typeName); + public native void setControllerButtonState(int index, int buttonCode, boolean pressed); + public native void setControllerAxisState(int index, int axisCode, float value); + public static native int getControllerButtonCode(String controllerType, String buttonName); + public static native int getControllerAxisCode(String controllerType, String axisName); 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 { @@ -42,6 +62,7 @@ public class AndroidHostInterface } static private AndroidHostInterface mInstance; + static public boolean createInstance(Context context) { // Set user path. String externalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath(); @@ -57,6 +78,7 @@ public class AndroidHostInterface static public boolean hasInstance() { return mInstance != null; } + static public AndroidHostInterface getInstance() { return mInstance; } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index 8497145ec..66ba697f7 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -3,6 +3,7 @@ package com.github.stenzek.duckstation; import android.annotation.SuppressLint; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; @@ -11,14 +12,12 @@ import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.Menu; -import android.view.MotionEvent; import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.MenuItem; import android.widget.FrameLayout; +import android.widget.Toast; -import androidx.core.app.NavUtils; import androidx.preference.PreferenceManager; /** @@ -30,77 +29,69 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde * Settings interfaces. */ 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(); } + private String getStringSetting(String key, String defaultValue) { return mPreferences.getString(key, defaultValue); } - /** - * Touchscreen controller overlay - */ - TouchscreenControllerView mTouchscreenController; - private boolean mTouchscreenControllerVisible = true; + public void reportError(String message) { + Log.e("EmulationActivity", message); + + Object lock = new Object(); + runOnUiThread(() -> { + // Toast.makeText(this, message, Toast.LENGTH_LONG); + new AlertDialog.Builder(this) + .setTitle("Error") + .setMessage(message) + .setPositiveButton("OK", (dialog, button) -> { + dialog.dismiss(); + synchronized (lock) { + lock.notify(); + } + }) + .create() + .show(); + }); - /** - * Whether or not the system UI should be auto-hidden after - * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. - */ - private static final boolean AUTO_HIDE = true; + synchronized (lock) { + try { + lock.wait(); + } catch (InterruptedException e) { + } + } + } - /** - * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after - * user interaction before hiding the system UI. - */ - private static final int AUTO_HIDE_DELAY_MILLIS = 3000; + public void reportMessage(String message) { + Log.i("EmulationActivity", message); + runOnUiThread(() -> { + Toast.makeText(this, message, Toast.LENGTH_SHORT); + }); + } - /** - * Some older devices needs a small delay between UI widget updates - * and a change of the status and navigation bar. - */ - private static final int UI_ANIMATION_DELAY = 300; - private final Handler mHideHandler = new Handler(); - private EmulationSurfaceView mContentView; - private final Runnable mHidePart2Runnable = new Runnable() { - @SuppressLint("InlinedApi") - @Override - public void run() { - // Delayed removal of status and navigation bar + public void onEmulationStarted() { + } - // Note that some of these constants are new as of API 16 (Jelly Bean) - // and API 19 (KitKat). It is safe to use them, as they are inlined - // at compile-time and do nothing on earlier devices. - mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - } - }; - private final Runnable mShowPart2Runnable = new Runnable() { - @Override - public void run() { - // Delayed display of UI elements - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.show(); - } - } - }; - private boolean mVisible; - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - hide(); - } - }; + public void onEmulationStopped() { + runOnUiThread(() -> { + finish(); + }); + } + + public void onGameTitleChanged(String title) { + runOnUiThread(() -> { + setTitle(title); + }); + } @Override public void surfaceCreated(SurfaceHolder holder) { @@ -114,16 +105,11 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde return; } - String bootPath = getIntent().getStringExtra("bootPath"); - String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath"); - boolean resumeState = getIntent().getBooleanExtra("resumeState", false); + final String bootPath = getIntent().getStringExtra("bootPath"); + final boolean resumeState = getIntent().getBooleanExtra("resumeState", false); + final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath"); - if (!AndroidHostInterface.getInstance() - .startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) { - Log.e("EmulationActivity", "Failed to start emulation thread"); - finishActivity(0); - return; - } + AndroidHostInterface.getInstance().startEmulationThread(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath); } @Override @@ -146,14 +132,14 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde actionBar.setDisplayHomeAsUpEnabled(true); } - mVisible = true; + mSystemUIVisible = true; mContentView = findViewById(R.id.fullscreen_content); mContentView.getHolder().addCallback(this); mContentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (mVisible) - hide(); + if (mSystemUIVisible) + hideSystemUI(); } }); @@ -173,11 +159,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); + hideSystemUI(); + } - // Trigger the initial hide() shortly after the activity has been - // created, to briefly hint to the user that UI controls - // are available. - delayedHide(100); + @Override + protected void onStop() { + super.onStop(); + + if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { + AndroidHostInterface.getInstance().stopEmulationThread(); + } } @Override @@ -228,37 +219,79 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde @Override public void onBackPressed() { - if (mVisible) { + if (mSystemUIVisible) { finish(); return; } - show(); + showSystemUI(); } - private void hide() { + /** + * Some older devices needs a small delay between UI widget updates + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + private final Handler mSystemUIHideHandler = new Handler(); + private EmulationSurfaceView mContentView; + private final Runnable mHidePart2Runnable = new Runnable() { + @SuppressLint("InlinedApi") + @Override + public void run() { + // Delayed removal of status and navigation bar + + // Note that some of these constants are new as of API 16 (Jelly Bean) + // and API 19 (KitKat). It is safe to use them, as they are inlined + // at compile-time and do nothing on earlier devices. + mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + }; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // Delayed display of UI elements + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + }; + private boolean mSystemUIVisible; + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hideSystemUI(); + } + }; + + private void hideSystemUI() { // Hide UI first ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.hide(); } - mVisible = false; + mSystemUIVisible = false; // Schedule a runnable to remove the status and navigation bar after a delay - mHideHandler.removeCallbacks(mShowPart2Runnable); - mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + mSystemUIHideHandler.removeCallbacks(mShowPart2Runnable); + mSystemUIHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); } @SuppressLint("InlinedApi") - private void show() { + private void showSystemUI() { // Show the system bar mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - mVisible = true; + mSystemUIVisible = true; // Schedule a runnable to display UI elements after a delay - mHideHandler.removeCallbacks(mHidePart2Runnable); - mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); + mSystemUIHideHandler.removeCallbacks(mHidePart2Runnable); + mSystemUIHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); } /** @@ -266,10 +299,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde * previously scheduled calls. */ private void delayedHide(int delayMillis) { - mHideHandler.removeCallbacks(mHideRunnable); - mHideHandler.postDelayed(mHideRunnable, delayMillis); + mSystemUIHideHandler.removeCallbacks(mHideRunnable); + mSystemUIHideHandler.postDelayed(mHideRunnable, delayMillis); } + /** + * Touchscreen controller overlay + */ + TouchscreenControllerView mTouchscreenController; + private boolean mTouchscreenControllerVisible = true; + private void setTouchscreenControllerVisibility(boolean visible) { mTouchscreenControllerVisible = visible; mTouchscreenController.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java index 98825fd55..5f1a098c6 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/FileUtil.java @@ -17,13 +17,13 @@ import java.lang.reflect.Array; import java.lang.reflect.Method; public final class FileUtil { - static String TAG="TAG"; + static String TAG = "TAG"; private static final String PRIMARY_VOLUME_NAME = "primary"; @Nullable public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) { if (treeUri == null) return null; - String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con); + String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con); if (volumePath == null) return File.separator; if (volumePath.endsWith(File.separator)) volumePath = volumePath.substring(0, volumePath.length() - 1); @@ -37,8 +37,7 @@ public final class FileUtil { return volumePath + documentPath; else return volumePath + File.separator + documentPath; - } - else return volumePath; + } else return volumePath; } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java b/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java index eb3fd8d63..e7d01a300 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/GameListEntry.java @@ -7,14 +7,12 @@ import android.widget.TextView; import androidx.core.content.ContextCompat; public class GameListEntry { - public enum EntryType - { + public enum EntryType { Disc, PSExe } - public enum CompatibilityRating - { + public enum CompatibilityRating { Unknown, DoesntBoot, CrashesInIntro, @@ -72,15 +70,21 @@ public class GameListEntry { return mTitle; } - public String getModifiedTime() { return mModifiedTime; } + public String getModifiedTime() { + return mModifiedTime; + } public DiscRegion getRegion() { return mRegion; } - public EntryType getType() { return mType; } + public EntryType getType() { + return mType; + } - public CompatibilityRating getCompatibilityRating() { return mCompatibilityRating; } + public CompatibilityRating getCompatibilityRating() { + return mCompatibilityRating; + } public void fillView(View view) { ((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java index 95d2fe8cd..be2063b55 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/MainActivity.java @@ -28,27 +28,26 @@ import android.view.MenuItem; import android.widget.AdapterView; import android.widget.ListView; import android.widget.PopupMenu; +import android.widget.Toast; import java.util.HashSet; import java.util.Set; import java.util.prefs.Preferences; +import static com.google.android.material.snackbar.Snackbar.make; + public class MainActivity extends AppCompatActivity { private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1; private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2; private GameList mGameList; private ListView mGameListView; + private boolean mHasExternalStoragePermissions = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) { - Log.i("MainActivity", "Failed to create host interface"); - throw new RuntimeException("Failed to create host interface"); - } - setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -93,6 +92,18 @@ public class MainActivity extends AppCompatActivity { return true; } }); + + mHasExternalStoragePermissions = checkForExternalStoragePermissions(); + if (mHasExternalStoragePermissions) + completeStartup(); + } + + private void completeStartup() { + if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) { + Log.i("MainActivity", "Failed to create host interface"); + throw new RuntimeException("Failed to create host interface"); + } + mGameList.refresh(false, false); } @@ -122,12 +133,17 @@ public class MainActivity extends AppCompatActivity { int id = item.getItemId(); //noinspection SimplifiableIfStatement - if (id == R.id.action_add_game_directory) { + if (id == R.id.action_resume) { + startEmulation(null, true); + } else if (id == R.id.action_start_bios) { + startEmulation(null, false); + } else 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_rescan_all_games) { + mGameList.refresh(true, true); } if (id == R.id.action_settings) { Intent intent = new Intent(this, SettingsActivity.class); @@ -190,19 +206,21 @@ public class MainActivity extends AppCompatActivity { int[] grantResults) { // check that all were successful for (int i = 0; i < grantResults.length; i++) { - if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { - Snackbar.make(mGameListView, - "External storage permissions are required to start emulation.", - Snackbar.LENGTH_LONG); + if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { + if (!mHasExternalStoragePermissions) { + mHasExternalStoragePermissions = true; + completeStartup(); + } + } else { + Toast.makeText(this, + "External storage permissions are required to use DuckStation.", + Toast.LENGTH_LONG); + finish(); } } } private boolean startEmulation(String bootPath, boolean resumeState) { - if (!checkForExternalStoragePermissions()) { - return false; - } - Intent intent = new Intent(this, EmulationActivity.class); intent.putExtra("bootPath", bootPath); intent.putExtra("resumeState", resumeState); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java index 2ace2d51b..b5986e3e0 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerButtonView.java @@ -19,25 +19,24 @@ public class TouchscreenControllerButtonView extends View { private String mButtonName = ""; private ButtonStateChangedListener mListener; - public interface ButtonStateChangedListener - { + public interface ButtonStateChangedListener { void onButtonStateChanged(TouchscreenControllerButtonView view, boolean pressed); } public TouchscreenControllerButtonView(Context context) { super(context); - init(context,null, 0); + init(context, null, 0); } public TouchscreenControllerButtonView(Context context, AttributeSet attrs) { super(context, attrs); - init(context,attrs, 0); + init(context, attrs, 0); } public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - init(context,attrs, defStyle); + init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { @@ -80,15 +79,12 @@ public class TouchscreenControllerButtonView extends View { } @Override - public boolean onTouchEvent(MotionEvent event) - { + public boolean onTouchEvent(MotionEvent event) { final boolean oldState = mPressed; - switch (event.getAction()) - { + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - { + case MotionEvent.ACTION_POINTER_DOWN: { mPressed = true; invalidate(); @@ -99,8 +95,7 @@ public class TouchscreenControllerButtonView extends View { } case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - { + case MotionEvent.ACTION_POINTER_UP: { mPressed = false; invalidate(); @@ -114,8 +109,7 @@ public class TouchscreenControllerButtonView extends View { return super.onTouchEvent(event); } - public boolean isPressed() - { + public boolean isPressed() { return mPressed; } @@ -127,13 +121,11 @@ public class TouchscreenControllerButtonView extends View { mButtonName = buttonName; } - public int getButtonCode() - { + public int getButtonCode() { return mButtonCode; } - public void setButtonCode(int code) - { + public void setButtonCode(int code) { mButtonCode = code; } @@ -153,8 +145,7 @@ public class TouchscreenControllerButtonView extends View { mUnpressedDrawable = unpressedDrawable; } - public void setButtonStateChangedListener(ButtonStateChangedListener listener) - { + public void setButtonStateChangedListener(ButtonStateChangedListener listener) { mListener = listener; } } diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java index 0c43b379e..1b7f475c4 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/TouchscreenControllerView.java @@ -50,9 +50,8 @@ public class TouchscreenControllerView extends FrameLayout implements Touchscree linkButton(view, R.id.controller_button_r2, "R2"); } - private void linkButton(View view, int id, String buttonName) - { - TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView)view.findViewById(id); + private void linkButton(View view, int id, String buttonName) { + TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id); buttonView.setButtonName(buttonName); buttonView.setButtonStateChangedListener(this); diff --git a/android/app/src/main/res/drawable/flag_eu.xml b/android/app/src/main/res/drawable/flag_eu.xml index a3bf3c7f0..e545a72a2 100644 --- a/android/app/src/main/res/drawable/flag_eu.xml +++ b/android/app/src/main/res/drawable/flag_eu.xml @@ -1,36 +1,70 @@ - - - - + + + - - - + + + - + - - - + + + - + android:strokeColor="#00000000" + android:strokeWidth="1"> - - - + + + diff --git a/android/app/src/main/res/drawable/flag_jp.xml b/android/app/src/main/res/drawable/flag_jp.xml index 8ff9d256f..f7b9fe6bd 100644 --- a/android/app/src/main/res/drawable/flag_jp.xml +++ b/android/app/src/main/res/drawable/flag_jp.xml @@ -1,26 +1,49 @@ - - - - + + + - - - + + + - + android:strokeColor="#00000000" + android:strokeWidth="1"> - - - + + + diff --git a/android/app/src/main/res/drawable/flag_us.xml b/android/app/src/main/res/drawable/flag_us.xml index 08c798d33..d83fcb9e2 100644 --- a/android/app/src/main/res/drawable/flag_us.xml +++ b/android/app/src/main/res/drawable/flag_us.xml @@ -1,5 +1,4 @@ - - + - + - + - + - + - + - + @@ -81,32 +104,56 @@ - + - + - + - + - + - + @@ -115,32 +162,56 @@ - + - + - + - + - + - + @@ -149,32 +220,56 @@ - + - + - + - + - + - + @@ -183,32 +278,56 @@ - + - + - + - + - + - + @@ -217,112 +336,200 @@ - - + + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + diff --git a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml index 13c137a92..41f4a52ed 100644 --- a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml +++ b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml @@ -4,7 +4,7 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_circle_button.xml b/android/app/src/main/res/drawable/ic_controller_circle_button.xml index c078d372c..d9fa460c6 100644 --- a/android/app/src/main/res/drawable/ic_controller_circle_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_circle_button.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml index a68b83f98..47953d0c1 100644 --- a/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_circle_button_pressed.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_cross_button.xml b/android/app/src/main/res/drawable/ic_controller_cross_button.xml index c0b10a80a..b133bd616 100644 --- a/android/app/src/main/res/drawable/ic_controller_cross_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_cross_button.xml @@ -3,25 +3,25 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml index 9f1766ec1..b00988473 100644 --- a/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_cross_button_pressed.xml @@ -3,25 +3,25 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_down_button.xml b/android/app/src/main/res/drawable/ic_controller_down_button.xml index b397fd762..898f2c54f 100644 --- a/android/app/src/main/res/drawable/ic_controller_down_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_down_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml index ecc4d7ae7..4e7d5a35e 100644 --- a/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_down_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_l1_button.xml b/android/app/src/main/res/drawable/ic_controller_l1_button.xml index 0bc81724b..9f3ab7240 100644 --- a/android/app/src/main/res/drawable/ic_controller_l1_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_l1_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml index 8c06f6467..06d625fe5 100644 --- a/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_l1_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_l2_button.xml b/android/app/src/main/res/drawable/ic_controller_l2_button.xml index 3ca495405..3853d103f 100644 --- a/android/app/src/main/res/drawable/ic_controller_l2_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_l2_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml index 05fd394f7..eeaefb18b 100644 --- a/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_l2_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_left_button.xml b/android/app/src/main/res/drawable/ic_controller_left_button.xml index 0dd3138b1..ccd46c3cc 100644 --- a/android/app/src/main/res/drawable/ic_controller_left_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_left_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml index 11907981a..39c1de9e1 100644 --- a/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_left_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_r1_button.xml b/android/app/src/main/res/drawable/ic_controller_r1_button.xml index a95517436..3130def38 100644 --- a/android/app/src/main/res/drawable/ic_controller_r1_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_r1_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml index 05fe52148..352ddbc6a 100644 --- a/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_r1_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_r2_button.xml b/android/app/src/main/res/drawable/ic_controller_r2_button.xml index 251b747f9..195fbe85d 100644 --- a/android/app/src/main/res/drawable/ic_controller_r2_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_r2_button.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml index c5fe48ef6..640c36863 100644 --- a/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_r2_button_pressed.xml @@ -3,26 +3,26 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - - - + + + diff --git a/android/app/src/main/res/drawable/ic_controller_right_button.xml b/android/app/src/main/res/drawable/ic_controller_right_button.xml index 586a11b79..8545a61ce 100644 --- a/android/app/src/main/res/drawable/ic_controller_right_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_right_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml index fabb570ae..f0cff05ba 100644 --- a/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_right_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_select_button.xml b/android/app/src/main/res/drawable/ic_controller_select_button.xml index ec850fc8a..bea56389c 100644 --- a/android/app/src/main/res/drawable/ic_controller_select_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_select_button.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml index 1f5bd9ca2..a34a925d4 100644 --- a/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_select_button_pressed.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_square_button.xml b/android/app/src/main/res/drawable/ic_controller_square_button.xml index 9ce49e148..0da658c01 100644 --- a/android/app/src/main/res/drawable/ic_controller_square_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_square_button.xml @@ -3,18 +3,18 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml index 731d2eed9..248a1d400 100644 --- a/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_square_button_pressed.xml @@ -3,18 +3,18 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_start_button.xml b/android/app/src/main/res/drawable/ic_controller_start_button.xml index e7e5d8ba7..7247bbcd7 100644 --- a/android/app/src/main/res/drawable/ic_controller_start_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_start_button.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml index 85f176935..5a8255577 100644 --- a/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_start_button_pressed.xml @@ -3,12 +3,12 @@ android:height="50dp" android:viewportWidth="26.458332" android:viewportHeight="13.229165"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_triangle_button.xml b/android/app/src/main/res/drawable/ic_controller_triangle_button.xml index 7645a89e7..9a1392988 100644 --- a/android/app/src/main/res/drawable/ic_controller_triangle_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_triangle_button.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml index 0bab1c10a..be10c7a85 100644 --- a/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_triangle_button_pressed.xml @@ -3,17 +3,17 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.45833"> - - + + diff --git a/android/app/src/main/res/drawable/ic_controller_up_button.xml b/android/app/src/main/res/drawable/ic_controller_up_button.xml index ad440f992..e8b5a4064 100644 --- a/android/app/src/main/res/drawable/ic_controller_up_button.xml +++ b/android/app/src/main/res/drawable/ic_controller_up_button.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml b/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml index 416454f5a..d17013321 100644 --- a/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml +++ b/android/app/src/main/res/drawable/ic_controller_up_button_pressed.xml @@ -3,12 +3,12 @@ android:height="100dp" android:viewportWidth="26.458332" android:viewportHeight="26.458332"> - + diff --git a/android/app/src/main/res/drawable/ic_emblem_system.xml b/android/app/src/main/res/drawable/ic_emblem_system.xml index 32b3d2397..4be483cfc 100644 --- a/android/app/src/main/res/drawable/ic_emblem_system.xml +++ b/android/app/src/main/res/drawable/ic_emblem_system.xml @@ -4,44 +4,42 @@ android:height="48dp" android:viewportWidth="48" android:viewportHeight="48"> - - - - - - + + + + diff --git a/android/app/src/main/res/drawable/ic_media_cdrom.xml b/android/app/src/main/res/drawable/ic_media_cdrom.xml index 74afde8b9..9269ea16f 100644 --- a/android/app/src/main/res/drawable/ic_media_cdrom.xml +++ b/android/app/src/main/res/drawable/ic_media_cdrom.xml @@ -4,107 +4,94 @@ android:height="48dp" android:viewportWidth="48" android:viewportHeight="48"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 963330c6e..a034d7c7e 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - - + android:layout_height="match_parent" + android:orientation="horizontal"> + + + + + + - + + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file diff --git a/android/app/src/main/res/menu/menu_emulation.xml b/android/app/src/main/res/menu/menu_emulation.xml index 439e736a5..c57b01b38 100644 --- a/android/app/src/main/res/menu/menu_emulation.xml +++ b/android/app/src/main/res/menu/menu_emulation.xml @@ -3,11 +3,14 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - - - diff --git a/android/app/src/main/res/menu/menu_game_list_entry.xml b/android/app/src/main/res/menu/menu_game_list_entry.xml index 45bbbb8c6..1fb01d997 100644 --- a/android/app/src/main/res/menu/menu_game_list_entry.xml +++ b/android/app/src/main/res/menu/menu_game_list_entry.xml @@ -13,8 +13,8 @@ android:title="Slow Boot" /> - + android:title="Load State"> + diff --git a/android/app/src/main/res/menu/menu_main.xml b/android/app/src/main/res/menu/menu_main.xml index dc942bbfd..9ad26feca 100644 --- a/android/app/src/main/res/menu/menu_main.xml +++ b/android/app/src/main/res/menu/menu_main.xml @@ -1,16 +1,28 @@ + tools:context="com.github.stenzek.duckstation.MainActivity"> + + + + - - - - diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 036d09bc5..c9ad5f98f 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09bc5..c9ad5f98f 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml index 66fbe92dc..c35f0db98 100644 --- a/android/app/src/main/res/values/arrays.xml +++ b/android/app/src/main/res/values/arrays.xml @@ -32,22 +32,22 @@ Software - 1x (1024x512 VRAM) - 2x (2048x1024 VRAM) - 3x (3072x1536 VRAM) - 4x (4096x2048 VRAM) - 5x (5120x2560 VRAM) - 6x (6144x3072 VRAM) - 7x (7168x3584 VRAM) - 8x (8192x4096 VRAM) - 9x (9216x4608 VRAM) - 10x (10240x5120 VRAM) - 11x (11264x5632 VRAM) - 12x (12288x6144 VRAM) - 13x (13312x6656 VRAM) - 14x (14336x7168 VRAM) - 15x (15360x7680 VRAM) - 16x (16384x8192 VRAM) + 1x + 2x + 3x (for 720p) + 4x + 5x (for 1080p) + 6x (for 1440p) + 7x + 8x + 9x (for 4K) + 10x + 11x + 12x + 13x + 14x + 15x + 16x 1 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4e41aacef..b849520af 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -30,11 +30,11 @@ Show VPS - Execution Mode + CPU Execution Mode Interpreter - Renderer + GPU Renderer Display Linear Filtering Resolution Scale True 24-Bit Color (Disables Dithering) diff --git a/android/app/src/main/res/xml/root_preferences.xml b/android/app/src/main/res/xml/root_preferences.xml index 6589670a5..c4fcb9e17 100644 --- a/android/app/src/main/res/xml/root_preferences.xml +++ b/android/app/src/main/res/xml/root_preferences.xml @@ -17,58 +17,43 @@ - - - - - - - - - - - - - - - - - + + app:summary="Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable." /> + app:key="Main/SaveStateOnExit" + app:title="Save State On Exit" + app:defaultValue="true" + app:summary="Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time." /> + + + + + + @@ -77,43 +62,43 @@ app:key="Display/ShowOSDMessages" app:title="@string/settings_osd_show_messages" app:defaultValue="true" - app:useSimpleSummaryProvider="true" /> + app:summary="Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc." /> + app:summary="Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage." /> + app:summary="Shows the internal frame rate of the game in the top-right corner of the display." /> + app:summary="Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display." /> - - - - + + + + + + + app:title="True Color Rendering (24-bit, disables dithering)" + app:summary="Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers." /> - - - + + app:key="GPU/DisableInterlacing" + app:title="Disable Interlacing (force progressive render/scan)" + app:defaultValue="true" + app:summary="Forces the rendering and display of frames to progressive mode. This removes the "combing" effect seen in 480i games by rendering them in 480p. Usually safe to enable." /> + app:key="GPU/TextureFiltering" + app:title="Bilinear Texture Filtering" + app:defaultValue="false" + app:summary="Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. Will have a greater effect on higher resolution scales. Only applies to the hardware renderers." /> + + - - + app:key="GPU/WidescreenHack" + app:title="Widescreen Hack" + app:defaultValue="false" + app:summary="Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to 16:9 in 3D games. For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. May not be compatible with all games." /> + app:defaultValue="false" + app:summary="Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. >Only works with the hardware renderers. May not be compatible with all games." /> - + app:defaultValue="true" + app:summary="Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled." /> + - - + app:summary="Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled." /> - + - - - - - - - + + - - + app:defaultValue="false" + app:summary="Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games." /> + @@ -224,7 +209,7 @@ app:title="Display Touchscreen Controller" app:defaultValue="true" /> - + + + + + + + +