From 365e9cd37fb72c19e2227cdca1abc91b255e16b2 Mon Sep 17 00:00:00 2001 From: FearlessTobi Date: Sat, 28 Mar 2020 19:44:26 +0100 Subject: [PATCH] Android: Run Directory Initialization as a thread instead of service Two reasons for this change. First, it appears that some android launchers do some sort of call into the application when long pressing the app icon, which in turn calls the DirectoryInit service. This was ok to do prior to Oreo but will cause crashes with the new restrictions on services running in the background. Which leads to the second reason that DirectoryInit doesn't need to be a service at all since these actions are required for dolphin to function and shouldn't be a scheduled action. So we instead just kick this off in a new thread and send the broadcast when done. Original commit by zackhow for Dolphin-emu --- src/android/app/src/main/AndroidManifest.xml | 2 +- .../org/citra/citra_emu/CitraApplication.java | 4 +- .../fragments/EmulationFragment.java | 14 +-- .../citra/citra_emu/ui/main/MainActivity.java | 4 +- .../citra_emu/ui/main/TvMainActivity.java | 4 +- .../ui/settings/SettingsActivity.java | 4 +- .../settings/SettingsActivityPresenter.java | 10 +- .../ui/settings/SettingsActivityView.java | 6 +- .../DirectoryInitialization.java} | 105 +++++++++--------- .../utils/DirectoryStateReceiver.java | 6 +- .../citra/citra_emu/utils/SettingsFile.java | 4 +- src/android/app/src/main/jni/native.cpp | 5 +- src/android/app/src/main/jni/native.h | 4 +- 13 files changed, 83 insertions(+), 89 deletions(-) rename src/android/app/src/main/java/org/citra/citra_emu/{services/DirectoryInitializationService.java => utils/DirectoryInitialization.java} (68%) diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 674ee214d..df1215e60 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -73,7 +73,7 @@ - + @@ -163,7 +163,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C LocalBroadcastManager.getInstance(getActivity()).registerReceiver( directoryStateReceiver, statusIntentFilter); - DirectoryInitializationService.startService(getActivity()); + DirectoryInitialization.start(getActivity()); } public void refreshInputOverlay() { @@ -190,8 +190,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C { final double[] perfStats = NativeLibrary.GetPerfStats(); if (perfStats[FPS] > 0) { - mPerfStats.setText(String.format("FPS: %d Speed: %d%%", (int)(perfStats[FPS] + 0.5), - (int)(perfStats[SPEED] * 100.0 + 0.5))); + mPerfStats.setText(String.format("FPS: %d Speed: %d%%", (int) (perfStats[FPS] + 0.5), + (int) (perfStats[SPEED] * 100.0 + 0.5))); } perfStatsUpdateHandler.postDelayed(perfStatsUpdater, 3000); diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.java index 30585257e..99c29ffc9 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.java @@ -15,7 +15,7 @@ import android.widget.Toast; import org.citra.citra_emu.R; import org.citra.citra_emu.activities.EmulationActivity; import org.citra.citra_emu.model.GameProvider; -import org.citra.citra_emu.services.DirectoryInitializationService; +import org.citra.citra_emu.utils.DirectoryInitialization; import org.citra.citra_emu.ui.platform.PlatformGamesFragment; import org.citra.citra_emu.ui.platform.PlatformGamesView; import org.citra.citra_emu.ui.settings.SettingsActivity; @@ -168,7 +168,7 @@ public final class MainActivity extends AppCompatActivity implements MainView { switch (requestCode) { case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - DirectoryInitializationService.startService(this); + DirectoryInitialization.start(this); mPlatformGamesFragment = new PlatformGamesFragment(); getSupportFragmentManager().beginTransaction().add(mFrameLayoutId, mPlatformGamesFragment) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/TvMainActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/TvMainActivity.java index 3ea8896f4..352f08949 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/TvMainActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/TvMainActivity.java @@ -23,7 +23,7 @@ import org.citra.citra_emu.adapters.GameRowPresenter; import org.citra.citra_emu.adapters.SettingsRowPresenter; import org.citra.citra_emu.model.Game; import org.citra.citra_emu.model.TvSettingsItem; -import org.citra.citra_emu.services.DirectoryInitializationService; +import org.citra.citra_emu.utils.DirectoryInitialization; import org.citra.citra_emu.ui.settings.SettingsActivity; import org.citra.citra_emu.utils.AddDirectoryHelper; import org.citra.citra_emu.utils.FileBrowserHelper; @@ -160,7 +160,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView { switch (requestCode) { case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - DirectoryInitializationService.startService(this); + DirectoryInitialization.start(this); loadGames(); } else { Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivity.java index e8aa935a8..e01d0b4d5 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivity.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivity.java @@ -16,7 +16,7 @@ import android.widget.Toast; import org.citra.citra_emu.R; import org.citra.citra_emu.model.settings.SettingSection; -import org.citra.citra_emu.services.DirectoryInitializationService; +import org.citra.citra_emu.utils.DirectoryInitialization; import org.citra.citra_emu.utils.DirectoryStateReceiver; import java.util.ArrayList; @@ -142,7 +142,7 @@ public final class SettingsActivity extends AppCompatActivity implements Setting LocalBroadcastManager.getInstance(this).registerReceiver( receiver, filter); - DirectoryInitializationService.startService(this); + DirectoryInitialization.start(this); } @Override diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityPresenter.java b/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityPresenter.java index b4df11354..04b843592 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityPresenter.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityPresenter.java @@ -6,8 +6,8 @@ import android.text.TextUtils; import org.citra.citra_emu.NativeLibrary; import org.citra.citra_emu.model.settings.SettingSection; -import org.citra.citra_emu.services.DirectoryInitializationService; -import org.citra.citra_emu.services.DirectoryInitializationService.DirectoryInitializationState; +import org.citra.citra_emu.utils.DirectoryInitialization; +import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState; import org.citra.citra_emu.utils.DirectoryStateReceiver; import org.citra.citra_emu.utils.Log; import org.citra.citra_emu.utils.SettingsFile; @@ -63,16 +63,16 @@ public final class SettingsActivityPresenter { } private void prepareDolphinDirectoriesIfNeeded() { - File configFile = new File(DirectoryInitializationService.getUserDirectory() + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini"); + File configFile = new File(DirectoryInitialization.getUserDirectory() + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini"); if (!configFile.exists()) { } - if (DirectoryInitializationService.areDolphinDirectoriesReady()) { + if (DirectoryInitialization.areDolphinDirectoriesReady()) { loadSettingsUI(); } else { mView.showLoading(); IntentFilter statusIntentFilter = new IntentFilter( - DirectoryInitializationService.BROADCAST_ACTION); + DirectoryInitialization.BROADCAST_ACTION); directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState -> diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityView.java b/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityView.java index 2b6924fd0..1785b6378 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityView.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/settings/SettingsActivityView.java @@ -124,15 +124,15 @@ public interface SettingsActivityView { void showExternalStorageNotMountedHint(); /** - * Start the DirectoryInitializationService and listen for the result. + * Start the DirectoryInitialization and listen for the result. * - * @param receiver the broadcast receiver for the DirectoryInitializationService + * @param receiver the broadcast receiver for the DirectoryInitialization * @param filter the Intent broadcasts to be received. */ void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter); /** - * Stop listening to the DirectoryInitializationService. + * Stop listening to the DirectoryInitialization. * * @param receiver The broadcast receiver to unregister. */ diff --git a/src/android/app/src/main/java/org/citra/citra_emu/services/DirectoryInitializationService.java b/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.java similarity index 68% rename from src/android/app/src/main/java/org/citra/citra_emu/services/DirectoryInitializationService.java rename to src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.java index 861961370..e47d1f4b6 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/services/DirectoryInitializationService.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.java @@ -4,9 +4,8 @@ * Refer to the license.txt file included. */ -package org.citra.citra_emu.services; +package org.citra.citra_emu.utils; -import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -15,8 +14,6 @@ import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; import org.citra.citra_emu.NativeLibrary; -import org.citra.citra_emu.utils.Log; -import org.citra.citra_emu.utils.PermissionsHandler; import java.io.File; import java.io.FileOutputStream; @@ -30,7 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * A service that spawns its own thread in order to copy several binary and shader files * from the Dolphin APK to the external file system. */ -public final class DirectoryInitializationService extends IntentService { +public final class DirectoryInitialization { public static final String BROADCAST_ACTION = "org.citra.citra_emu.BROADCAST"; public static final String EXTRA_STATE = "directoryState"; @@ -38,14 +35,33 @@ public final class DirectoryInitializationService extends IntentService { private static String userPath; private static AtomicBoolean isDolphinDirectoryInitializationRunning = new AtomicBoolean(false); - public DirectoryInitializationService() { - // Superclass constructor is called to name the thread on which this service executes. - super("DirectoryInitializationService"); + public static void start(Context context) { + // Can take a few seconds to run, so don't block UI thread. + //noinspection TrivialFunctionalExpressionUsage + ((Runnable) () -> init(context)).run(); } - public static void startService(Context context) { - Intent intent = new Intent(context, DirectoryInitializationService.class); - context.startService(intent); + private static void init(Context context) { + if (!isDolphinDirectoryInitializationRunning.compareAndSet(false, true)) + return; + + if (directoryState != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) { + if (PermissionsHandler.hasWriteAccess(context)) { + if (setDolphinUserDirectory()) { + initializeInternalStorage(context); + CreateUserDirectories(); + NativeLibrary.CreateConfigFile(); + directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED; + } else { + directoryState = DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE; + } + } else { + directoryState = DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED; + } + } + + isDolphinDirectoryInitializationRunning.set(false); + sendBroadcastState(directoryState, context); } private static void deleteDirectoryRecursively(File file) { @@ -62,10 +78,10 @@ public final class DirectoryInitializationService extends IntentService { public static String getUserDirectory() { if (directoryState == null) { - throw new IllegalStateException("DirectoryInitializationService has to run at least once!"); + throw new IllegalStateException("DirectoryInitialization has to run at least once!"); } else if (isDolphinDirectoryInitializationRunning.get()) { throw new IllegalStateException( - "DirectoryInitializationService has to finish running first!"); + "DirectoryInitialization has to finish running first!"); } return userPath; @@ -75,35 +91,12 @@ public final class DirectoryInitializationService extends IntentService { private static native void SetSysDirectory(String path); - @Override - protected void onHandleIntent(Intent intent) { - isDolphinDirectoryInitializationRunning.set(true); - - if (directoryState != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) { - if (PermissionsHandler.hasWriteAccess(this)) { - if (setDolphinUserDirectory()) { - initializeInternalStorage(); - CreateUserDirectories(); - NativeLibrary.CreateConfigFile(); - directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED; - } else { - directoryState = DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE; - } - } else { - directoryState = DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED; - } - } - - isDolphinDirectoryInitializationRunning.set(false); - sendBroadcastState(directoryState); - } - - private boolean setDolphinUserDirectory() { + private static boolean setDolphinUserDirectory() { if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { File externalPath = Environment.getExternalStorageDirectory(); if (externalPath != null) { userPath = externalPath.getAbsolutePath() + "/citra-emu"; - Log.debug("[DirectoryInitializationService] User Dir: " + userPath); + Log.debug("[DirectoryInitialization] User Dir: " + userPath); // NativeLibrary.SetUserDirectory(userPath); return true; } @@ -113,16 +106,16 @@ public final class DirectoryInitializationService extends IntentService { return false; } - private void initializeInternalStorage() { - File sysDirectory = new File(getFilesDir(), "Sys"); + private static void initializeInternalStorage(Context context) { + File sysDirectory = new File(context.getFilesDir(), "Sys"); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); String revision = NativeLibrary.GetGitRevision(); if (!preferences.getString("sysDirectoryVersion", "").equals(revision)) { // There is no extracted Sys directory, or there is a Sys directory from another // version of Dolphin that might contain outdated files. Let's (re-)extract Sys. deleteDirectoryRecursively(sysDirectory); - copyAssetFolder("Sys", sysDirectory, true); + copyAssetFolder("Sys", sysDirectory, true, context); SharedPreferences.Editor editor = preferences.edit(); editor.putString("sysDirectoryVersion", revision); @@ -133,52 +126,54 @@ public final class DirectoryInitializationService extends IntentService { SetSysDirectory(sysDirectory.getPath()); } - private void sendBroadcastState(DirectoryInitializationState state) { + private static void sendBroadcastState(DirectoryInitializationState state, Context context) { Intent localIntent = new Intent(BROADCAST_ACTION) .putExtra(EXTRA_STATE, state); - LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent); + LocalBroadcastManager.getInstance(context).sendBroadcast(localIntent); } - private void copyAsset(String asset, File output, Boolean overwrite) { - Log.verbose("[DirectoryInitializationService] Copying File " + asset + " to " + output); + private static void copyAsset(String asset, File output, Boolean overwrite, Context context) { + Log.verbose("[DirectoryInitialization] Copying File " + asset + " to " + output); try { if (!output.exists() || overwrite) { - InputStream in = getAssets().open(asset); + InputStream in = context.getAssets().open(asset); OutputStream out = new FileOutputStream(output); copyFile(in, out); in.close(); out.close(); } } catch (IOException e) { - Log.error("[DirectoryInitializationService] Failed to copy asset file: " + asset + + Log.error("[DirectoryInitialization] Failed to copy asset file: " + asset + e.getMessage()); } } - private void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite) { - Log.verbose("[DirectoryInitializationService] Copying Folder " + assetFolder + " to " + + private static void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite, + Context context) { + Log.verbose("[DirectoryInitialization] Copying Folder " + assetFolder + " to " + outputFolder); try { boolean createdFolder = false; - for (String file : getAssets().list(assetFolder)) { + for (String file : context.getAssets().list(assetFolder)) { if (!createdFolder) { outputFolder.mkdir(); createdFolder = true; } copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file), - overwrite); - copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite); + overwrite, context); + copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite, + context); } } catch (IOException e) { - Log.error("[DirectoryInitializationService] Failed to copy asset folder: " + assetFolder + + Log.error("[DirectoryInitialization] Failed to copy asset folder: " + assetFolder + e.getMessage()); } } - private void copyFile(InputStream in, OutputStream out) throws IOException { + private static void copyFile(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int read; diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryStateReceiver.java b/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryStateReceiver.java index 60c7931f2..851f1e1d6 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryStateReceiver.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryStateReceiver.java @@ -4,8 +4,8 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import org.citra.citra_emu.services.DirectoryInitializationService; -import org.citra.citra_emu.services.DirectoryInitializationService.DirectoryInitializationState; +import org.citra.citra_emu.utils.DirectoryInitialization; +import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState; import rx.functions.Action1; @@ -19,7 +19,7 @@ public class DirectoryStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { DirectoryInitializationState state = (DirectoryInitializationState) intent - .getSerializableExtra(DirectoryInitializationService.EXTRA_STATE); + .getSerializableExtra(DirectoryInitialization.EXTRA_STATE); callback.call(state); } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/SettingsFile.java b/src/android/app/src/main/java/org/citra/citra_emu/utils/SettingsFile.java index 044469530..1e64b477e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/SettingsFile.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/SettingsFile.java @@ -7,7 +7,7 @@ import org.citra.citra_emu.model.settings.IntSetting; import org.citra.citra_emu.model.settings.Setting; import org.citra.citra_emu.model.settings.SettingSection; import org.citra.citra_emu.model.settings.StringSetting; -import org.citra.citra_emu.services.DirectoryInitializationService; +import org.citra.citra_emu.utils.DirectoryInitialization; import org.citra.citra_emu.ui.settings.SettingsActivityView; import org.ini4j.Wini; @@ -212,7 +212,7 @@ public final class SettingsFile { @NonNull private static File getSettingsFile(String fileName) { return new File( - DirectoryInitializationService.getUserDirectory() + "/config/" + fileName + ".ini"); + DirectoryInitialization.getUserDirectory() + "/config/" + fileName + ".ini"); } private static SettingSection sectionFromLine(String line) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index ff38430a0..c8a028e7d 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -374,11 +374,10 @@ void Java_org_citra_citra_1emu_NativeLibrary_LoadState(JNIEnv* env, jobject obj, void Java_org_citra_citra_1emu_NativeLibrary_LoadStateAs(JNIEnv* env, jobject obj, jstring path) {} -void Java_org_citra_citra_1emu_services_DirectoryInitializationService_CreateUserDirectories( +void Java_org_citra_citra_1emu_utils_DirectoryInitialization_CreateUserDirectories( JNIEnv* env, jobject obj) {} jstring Java_org_citra_citra_1emu_NativeLibrary_GetUserDirectory(JNIEnv* env, jobject obj) { - return nullptr; } @@ -461,7 +460,7 @@ jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_GetPerfStats(JNIEnv* env, j return jstats; } -void Java_org_citra_citra_1emu_services_DirectoryInitializationService_SetSysDirectory( +void Java_org_citra_citra_1emu_utils_DirectoryInitialization_SetSysDirectory( JNIEnv* env, jclass type, jstring path_) { const char* path = env->GetStringUTFChars(path_, 0); diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 535d8214c..a544bd9c4 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -109,14 +109,14 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_LoadStateAs(JNIEn jstring path); JNIEXPORT void JNICALL -Java_org_citra_citra_1emu_services_DirectoryInitializationService_CreateUserDirectories( +Java_org_citra_citra_1emu_utils_DirectoryInitialization_CreateUserDirectories( JNIEnv* env, jobject obj); JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory( JNIEnv* env, jobject obj, jstring jDirectory); JNIEXPORT void JNICALL -Java_org_citra_citra_1emu_services_DirectoryInitializationService_SetSysDirectory( +Java_org_citra_citra_1emu_utils_DirectoryInitialization_SetSysDirectory( JNIEnv* env, jclass type, jstring path_); JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetUserDirectory(JNIEnv* env,