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
This commit is contained in:
FearlessTobi 2020-03-28 19:44:26 +01:00 committed by bunnei
parent 09e886325f
commit e42dffba64
13 changed files with 83 additions and 89 deletions

View File

@ -73,7 +73,7 @@
</intent-filter>
</activity>
<service android:name="org.citra.citra_emu.services.DirectoryInitializationService"/>
<service android:name="org.citra.citra_emu.utils.DirectoryInitialization"/>
<provider
android:name="org.citra.citra_emu.model.GameProvider"

View File

@ -11,7 +11,7 @@ import android.content.Context;
import android.os.Build;
import org.citra.citra_emu.model.GameDatabase;
import org.citra.citra_emu.services.DirectoryInitializationService;
import org.citra.citra_emu.utils.DirectoryInitialization;
import org.citra.citra_emu.utils.PermissionsHandler;
public class CitraApplication extends Application {
@ -41,7 +41,7 @@ public class CitraApplication extends Application {
application = this;
if (PermissionsHandler.hasWriteAccess(getApplicationContext())) {
DirectoryInitializationService.startService(getApplicationContext());
DirectoryInitialization.start(getApplicationContext());
}
createNotificationChannel();

View File

@ -22,8 +22,8 @@ import org.citra.citra_emu.NativeLibrary;
import org.citra.citra_emu.R;
import org.citra.citra_emu.activities.EmulationActivity;
import org.citra.citra_emu.overlay.InputOverlay;
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.EmulationMenuSettings;
import org.citra.citra_emu.utils.Log;
@ -113,7 +113,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
@Override
public void onResume() {
super.onResume();
if (DirectoryInitializationService.areDolphinDirectoriesReady()) {
if (DirectoryInitialization.areDolphinDirectoriesReady()) {
mEmulationState.run(activity.isActivityRecreated());
} else {
setupDolphinDirectoriesThenStartEmulation();
@ -139,7 +139,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
private void setupDolphinDirectoriesThenStartEmulation() {
IntentFilter statusIntentFilter = new IntentFilter(
DirectoryInitializationService.BROADCAST_ACTION);
DirectoryInitialization.BROADCAST_ACTION);
directoryStateReceiver =
new DirectoryStateReceiver(directoryInitializationState ->
@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 ->

View File

@ -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.
*/

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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,