From 47e1108d572b48ef213c0a3d0e354f69a99c7c79 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 16 Jun 2019 15:32:36 +0200 Subject: [PATCH 1/4] Android: Add helper class AfterDirectoryInitializationRunner --- .../AfterDirectoryInitializationRunner.java | 50 +++++++++++++++++++ .../dolphinemu/utils/Analytics.java | 29 +---------- 2 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AfterDirectoryInitializationRunner.java diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AfterDirectoryInitializationRunner.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AfterDirectoryInitializationRunner.java new file mode 100644 index 0000000000..0ce87b2b8a --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AfterDirectoryInitializationRunner.java @@ -0,0 +1,50 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.content.Context; +import android.content.IntentFilter; +import android.support.v4.content.LocalBroadcastManager; + +public class AfterDirectoryInitializationRunner +{ + private DirectoryStateReceiver directoryStateReceiver; + + /** + * Executes a Runnable after directory initialization has finished. + * + * If this is called when directory initialization already is done, + * the Runnable will be executed immediately. If this is called before + * directory initialization is done, the Runnable will be executed + * after directory initialization finishes successfully, or never + * in case directory initialization doesn't finish successfully. + * + * Calling this function multiple times per object is not supported. + */ + public void run(Context context, Runnable runnable) + { + if (!DirectoryInitialization.areDolphinDirectoriesReady()) + { + // Wait for directories to get initialized + IntentFilter statusIntentFilter = new IntentFilter( + DirectoryInitialization.BROADCAST_ACTION); + + directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState -> + { + if (directoryInitializationState == + DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) + { + LocalBroadcastManager.getInstance(context).unregisterReceiver(directoryStateReceiver); + directoryStateReceiver = null; + runnable.run(); + } + }); + // Registers the DirectoryStateReceiver and its intent filters + LocalBroadcastManager.getInstance(context).registerReceiver( + directoryStateReceiver, + statusIntentFilter); + } + else + { + runnable.run(); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java index 6f3e07cbb5..cb7aec2056 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Analytics.java @@ -18,8 +18,6 @@ import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; public class Analytics { - private static DirectoryStateReceiver directoryStateReceiver; - private static final String analyticsAsked = Settings.SECTION_ANALYTICS + "_" + SettingsFile.KEY_ANALYTICS_PERMISSION_ASKED; private static final String analyticsEnabled = @@ -35,31 +33,8 @@ public class Analytics SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); if (!preferences.getBoolean(analyticsAsked, false)) { - if (!DirectoryInitialization.areDolphinDirectoriesReady()) - { - // Wait for directories to get initialized - IntentFilter statusIntentFilter = new IntentFilter( - DirectoryInitialization.BROADCAST_ACTION); - - directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState -> - { - if (directoryInitializationState == - DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) - { - LocalBroadcastManager.getInstance(context).unregisterReceiver(directoryStateReceiver); - directoryStateReceiver = null; - showMessage(context, preferences); - } - }); - // Registers the DirectoryStateReceiver and its intent filters - LocalBroadcastManager.getInstance(context).registerReceiver( - directoryStateReceiver, - statusIntentFilter); - } - else - { - showMessage(context, preferences); - } + new AfterDirectoryInitializationRunner().run(context, + () -> showMessage(context, preferences)); } } From 9f3f45aa5fa647b1304462051dbb4ba9ccca4bce Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 16 Jun 2019 14:41:58 +0200 Subject: [PATCH 2/4] Android: Call UICommon::Init at app start instead of emulation start Much of our native code assumes that UICommon::Init has been called (for reasons such as wanting to access the user's settings), so not calling it until emulation start heavily limits what native code we can use in the Android GUI (except during emulation). --- .../dolphinemu/dolphinemu/NativeLibrary.java | 21 +++++++- .../features/settings/model/Settings.java | 4 ++ .../fragments/EmulationFragment.java | 13 ++--- .../utils/DirectoryInitialization.java | 2 + .../dolphinemu/utils/StartupHandler.java | 14 +++-- Source/Android/jni/MainAndroid.cpp | 52 ++++++++++++------- 6 files changed, 67 insertions(+), 39 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 174d2d1ff2..dc3c48c338 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -349,10 +349,29 @@ public final class NativeLibrary public static native int DefaultCPUCore(); + public static native void ReloadConfig(); + + /** + * Initializes the native parts of the app. + * + * Should be called at app start before running any other native code + * (other than the native methods in DirectoryInitialization). + */ + public static native void Initialize(); + + /** + * Tells analytics that Dolphin has been started. + * + * Since users typically don't explicitly close Android apps, it's appropriate to + * call this not only when the app starts but also when the user returns to the app + * after not using it for a significant amount of time. + */ + public static native void ReportStartToAnalytics(); + /** * Begins emulation. */ - public static native void Run(String[] path, boolean firstOpen); + public static native void Run(String[] path); /** * Begins emulation from the specified savestate. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java index ca34fc575b..d3cb2b6c66 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.java @@ -2,6 +2,7 @@ package org.dolphinemu.dolphinemu.features.settings.model; import android.text.TextUtils; +import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView; import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; @@ -175,6 +176,9 @@ public class Settings SettingsFile.saveFile(fileName, iniSections, view); } + + // Notify the native code of the changes + NativeLibrary.ReloadConfig(); } else { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index f817414add..1ff9ba8ea0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -82,12 +82,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); String[] gamePaths = getArguments().getStringArray(KEY_GAMEPATHS); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); - boolean firstOpen = preferences.getBoolean(StartupHandler.NEW_SESSION, true); - SharedPreferences.Editor sPrefsEditor = preferences.edit(); - sPrefsEditor.putBoolean(StartupHandler.NEW_SESSION, false); - sPrefsEditor.apply(); - mEmulationState = new EmulationState(gamePaths, getTemporaryStateFilePath(), firstOpen); + mEmulationState = new EmulationState(gamePaths, getTemporaryStateFilePath()); } /** @@ -271,12 +266,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private Surface mSurface; private boolean mRunWhenSurfaceIsValid; private boolean loadPreviousTemporaryState; - private boolean firstOpen; private final String temporaryStatePath; - EmulationState(String[] gamePaths, String temporaryStatePath, boolean firstOpen) + EmulationState(String[] gamePaths, String temporaryStatePath) { - this.firstOpen = firstOpen; mGamePaths = gamePaths; this.temporaryStatePath = temporaryStatePath; // Starting state is stopped. @@ -420,7 +413,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C else { Log.debug("[EmulationFragment] Starting emulation thread."); - NativeLibrary.Run(mGamePaths, firstOpen); + NativeLibrary.Run(mGamePaths); } }, "NativeEmulation"); mEmulationThread.start(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java index 47925df998..727240b478 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java @@ -67,6 +67,8 @@ public final class DirectoryInitialization { initializeInternalStorage(context); initializeExternalStorage(context); + NativeLibrary.Initialize(); + NativeLibrary.ReportStartToAnalytics(); directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED; } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java index 8631e5ed8d..cdddd4cd37 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/StartupHandler.java @@ -8,15 +8,15 @@ import android.preference.PreferenceManager; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; +import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.activities.EmulationActivity; import java.util.Date; public final class StartupHandler { - public static final String NEW_SESSION = "NEW_SESSION"; public static final String LAST_CLOSED = "LAST_CLOSED"; - public static final Long SESSION_TIMEOUT = 21600000L; // 6 hours in milliseconds + public static final long SESSION_TIMEOUT = 21600000L; // 6 hours in milliseconds public static void HandleInit(FragmentActivity parent) { @@ -66,15 +66,13 @@ public final class StartupHandler */ public static void checkSessionReset(Context context) { - Long currentTime = new Date(System.currentTimeMillis()).getTime(); + long currentTime = new Date(System.currentTimeMillis()).getTime(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - Long lastOpen = preferences.getLong(LAST_CLOSED, 0); + long lastOpen = preferences.getLong(LAST_CLOSED, 0); if (currentTime > (lastOpen + SESSION_TIMEOUT)) { - // Passed at emulation start to trigger first open event. - SharedPreferences.Editor sPrefsEditor = preferences.edit(); - sPrefsEditor.putBoolean(NEW_SESSION, true); - sPrefsEditor.apply(); + new AfterDirectoryInitializationRunner().run(context, + () -> NativeLibrary.ReportStartToAnalytics()); } } } diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 3fcae32df8..e1b19314c3 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -245,12 +245,14 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_DefaultCPUCo JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfiling(JNIEnv* env, jobject obj, jboolean enable); +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Initialize(JNIEnv* env, + jobject obj); +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_NativeLibrary_ReportStartToAnalytics(JNIEnv* env, jobject obj); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfileResults(JNIEnv* env, jobject obj); - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Z( - JNIEnv* env, jobject obj, jstring jFile, jboolean jfirstOpen); - +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2( + JNIEnv* env, jobject obj, jstring jFile); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z( JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate, jboolean jDeleteSavestate); @@ -585,6 +587,27 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ReloadWiimot Wiimote::LoadConfig(); } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ReloadConfig(JNIEnv* env, + jobject obj) +{ + SConfig::GetInstance().LoadSettings(); +} + +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Initialize(JNIEnv* env, + jobject obj) +{ + Common::RegisterMsgAlertHandler(&MsgAlert); + Common::AndroidSetReportHandler(&ReportSend); + DolphinAnalytics::AndroidSetGetValFunc(&GetAnalyticValue); + UICommon::Init(); +} + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_NativeLibrary_ReportStartToAnalytics(JNIEnv* env, jobject obj) +{ + DolphinAnalytics::Instance().ReportDolphinStart(GetAnalyticValue("DEVICE_TYPE")); +} + // Returns the scale factor for imgui rendering. // Based on the scaledDensity of the device's display metrics. static float GetRenderSurfaceScale(JNIEnv* env) @@ -630,23 +653,13 @@ static float GetRenderSurfaceScale(JNIEnv* env) return scaled_density; } -static void Run(JNIEnv* env, const std::vector& paths, bool first_open, +static void Run(JNIEnv* env, const std::vector& paths, std::optional savestate_path = {}, bool delete_savestate = false) { ASSERT(!paths.empty()); __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str()); - Common::RegisterMsgAlertHandler(&MsgAlert); - Common::AndroidSetReportHandler(&ReportSend); - DolphinAnalytics::AndroidSetGetValFunc(&GetAnalyticValue); - std::unique_lock guard(s_host_identity_lock); - UICommon::Init(); - - if (first_open) - { - DolphinAnalytics::Instance().ReportDolphinStart(GetAnalyticValue("DEVICE_TYPE")); - } WiimoteReal::InitAdapterClass(); @@ -679,7 +692,6 @@ static void Run(JNIEnv* env, const std::vector& paths, bool first_o Core::Shutdown(); ButtonManager::Shutdown(); - UICommon::Shutdown(); guard.unlock(); if (s_surf) @@ -689,17 +701,17 @@ static void Run(JNIEnv* env, const std::vector& paths, bool first_o } } -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Z( - JNIEnv* env, jobject obj, jobjectArray jPaths, jboolean jfirstOpen) +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2( + JNIEnv* env, jobject obj, jobjectArray jPaths) { - Run(env, JStringArrayToVector(env, jPaths), jfirstOpen); + Run(env, JStringArrayToVector(env, jPaths)); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Ljava_lang_String_2Z( JNIEnv* env, jobject obj, jobjectArray jPaths, jstring jSavestate, jboolean jDeleteSavestate) { - Run(env, JStringArrayToVector(env, jPaths), false, GetJString(env, jSavestate), jDeleteSavestate); + Run(env, JStringArrayToVector(env, jPaths), GetJString(env, jSavestate), jDeleteSavestate); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ChangeDisc(JNIEnv* env, From c677268aaf8002c11c1a1c55a0d2938de512401c Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 16 Jun 2019 16:35:58 +0200 Subject: [PATCH 3/4] Android: Don't use GameFile/GameFileCache before UICommon::Init Preparation for the next commit. --- .../dolphinemu/services/GameFileCacheService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java index 17aef0cd35..a58eeb1009 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java @@ -8,6 +8,7 @@ import android.support.v4.content.LocalBroadcastManager; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.model.GameFileCache; import org.dolphinemu.dolphinemu.ui.platform.Platform; +import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner; import java.io.File; import java.util.ArrayList; @@ -94,7 +95,8 @@ public final class GameFileCacheService extends IntentService */ public static void startLoad(Context context) { - startService(context, ACTION_LOAD); + new AfterDirectoryInitializationRunner().run(context, + () -> startService(context, ACTION_LOAD)); } /** @@ -104,7 +106,8 @@ public final class GameFileCacheService extends IntentService */ public static void startRescan(Context context) { - startService(context, ACTION_RESCAN); + new AfterDirectoryInitializationRunner().run(context, + () -> startService(context, ACTION_RESCAN)); } public static GameFile addOrGet(String gamePath) From f79ca651701fdbfe1a95aa09bd7c3d0a68ed4eeb Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 16 Jun 2019 16:38:26 +0200 Subject: [PATCH 4/4] UICommon: Remove Android hacks from GameFile --- Source/Core/UICommon/GameFile.cpp | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index 89d38e24f5..f5aefd7f01 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -46,28 +46,11 @@ namespace constexpr char COVER_URL[] = "https://art.gametdb.com/wii/cover/%s/%s.png"; const std::string EMPTY_STRING; - -bool UseGameCovers() -{ -// We ifdef this out on Android because accessing the config before emulation start makes us crash. -// The Android GUI handles covers in Java anyway, so this doesn't make us lose any functionality. -#ifdef ANDROID - return false; -#else - return Config::Get(Config::MAIN_USE_GAME_COVERS); -#endif -} } // Anonymous namespace DiscIO::Language GameFile::GetConfigLanguage() const { -#ifdef ANDROID - // TODO: Make the Android app load the config at app start instead of emulation start - // so that we can access the user's preference here - return DiscIO::Language::English; -#else return SConfig::GetInstance().GetLanguageAdjustedForRegion(DiscIO::IsWii(m_platform), m_region); -#endif } bool operator==(const GameBanner& lhs, const GameBanner& rhs) @@ -174,7 +157,7 @@ bool GameFile::IsValid() const bool GameFile::CustomCoverChanged() { - if (!m_custom_cover.buffer.empty() || !UseGameCovers()) + if (!m_custom_cover.buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) return false; std::string path, name; @@ -201,7 +184,7 @@ bool GameFile::CustomCoverChanged() void GameFile::DownloadDefaultCover() { - if (!m_default_cover.buffer.empty() || !UseGameCovers()) + if (!m_default_cover.buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) return; const auto cover_path = File::GetUserPath(D_COVERCACHE_IDX) + DIR_SEP; @@ -267,7 +250,7 @@ void GameFile::DownloadDefaultCover() bool GameFile::DefaultCoverChanged() { - if (!m_default_cover.buffer.empty() || !UseGameCovers()) + if (!m_default_cover.buffer.empty() || !Config::Get(Config::MAIN_USE_GAME_COVERS)) return false; const auto cover_path = File::GetUserPath(D_COVERCACHE_IDX) + DIR_SEP;