From 1311f84706041d313abb4dbf2e0ca50893399e80 Mon Sep 17 00:00:00 2001 From: zackhow Date: Fri, 14 Sep 2018 00:46:30 -0400 Subject: 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. --- Source/Android/app/src/main/AndroidManifest.xml | 2 +- .../dolphinemu/dolphinemu/DolphinApplication.java | 4 +- .../dolphinemu/activities/AppLinkActivity.java | 12 +- .../dolphinemu/adapters/GameAdapter.java | 4 +- .../dolphinemu/adapters/GameRowPresenter.java | 4 +- .../features/settings/ui/SettingsActivity.java | 4 +- .../settings/ui/SettingsActivityPresenter.java | 8 +- .../features/settings/ui/SettingsActivityView.java | 6 +- .../settings/ui/SettingsFragmentPresenter.java | 4 +- .../features/settings/utils/SettingsFile.java | 10 +- .../dolphinemu/fragments/EmulationFragment.java | 10 +- .../services/DirectoryInitializationService.java | 270 --------------------- .../dolphinemu/ui/main/MainActivity.java | 4 +- .../dolphinemu/ui/main/TvMainActivity.java | 6 +- .../org/dolphinemu/dolphinemu/utils/Analytics.java | 7 +- .../dolphinemu/utils/DirectoryInitialization.java | 264 ++++++++++++++++++++ .../dolphinemu/utils/DirectoryStateReceiver.java | 5 +- Source/Android/jni/MainAndroid.cpp | 11 +- 18 files changed, 313 insertions(+), 322 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java (limited to 'Source/Android') diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml index 975a493860..8bf06262da 100644 --- a/Source/Android/app/src/main/AndroidManifest.xml +++ b/Source/Android/app/src/main/AndroidManifest.xml @@ -84,7 +84,7 @@ - + { if (directoryInitializationState == - DirectoryInitializationService.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) + DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) { play(playAction); } else if (directoryInitializationState == - DirectoryInitializationService.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) + DirectoryInitialization.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) { Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) .show(); } else if (directoryInitializationState == - DirectoryInitializationService.DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE) + DirectoryInitialization.DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE) { Toast.makeText(this, R.string.external_storage_not_mounted, Toast.LENGTH_SHORT) .show(); @@ -92,7 +92,7 @@ public class AppLinkActivity extends FragmentActivity LocalBroadcastManager.getInstance(this).registerReceiver( directoryStateReceiver, statusIntentFilter); - DirectoryInitializationService.startService(this); + DirectoryInitialization.start(this); GameFileCacheService.startLoad(this); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java index 4b07d834fd..17ae613be6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java @@ -15,7 +15,7 @@ import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; import org.dolphinemu.dolphinemu.model.GameFile; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.PicassoUtils; import org.dolphinemu.dolphinemu.viewholders.GameViewHolder; @@ -166,7 +166,7 @@ public final class GameAdapter extends RecyclerView.Adapter impl break; case 2: String path = - DirectoryInitializationService.getUserDirectory() + "/GameSettings/" + + DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini"; File gameSettingsFile = new File(path); if (gameSettingsFile.exists()) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java index 0cbf0a9cfd..2cf23080b1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java @@ -17,7 +17,7 @@ import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; import org.dolphinemu.dolphinemu.model.GameFile; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.utils.PicassoUtils; import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; @@ -113,7 +113,7 @@ public final class GameRowPresenter extends Presenter SettingsActivity.launch(activity, MenuTag.GRAPHICS, gameId); break; case 2: - String path = DirectoryInitializationService.getUserDirectory() + + String path = DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini"; File gameSettingsFile = new File(path); if (gameSettingsFile.exists()) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java index 256b38f22a..78b8ef2d1f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.java @@ -15,7 +15,7 @@ import android.view.MenuItem; import android.widget.Toast; import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver; public final class SettingsActivity extends AppCompatActivity implements SettingsActivityView @@ -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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.java index acf1abd7dd..57bbe9d6cc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityPresenter.java @@ -6,8 +6,8 @@ import android.text.TextUtils; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState; import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver; import org.dolphinemu.dolphinemu.utils.Log; @@ -71,7 +71,7 @@ public final class SettingsActivityPresenter private void prepareDolphinDirectoriesIfNeeded() { - if (DirectoryInitializationService.areDolphinDirectoriesReady()) + if (DirectoryInitialization.areDolphinDirectoriesReady()) { loadSettingsUI(); } @@ -79,7 +79,7 @@ public final class SettingsActivityPresenter { mView.showLoading(); IntentFilter statusIntentFilter = new IntentFilter( - DirectoryInitializationService.BROADCAST_ACTION); + DirectoryInitialization.BROADCAST_ACTION); directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState -> diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java index 278c732556..910c9f0014 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivityView.java @@ -120,15 +120,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/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java index 1aaf8eb4a3..39e380f446 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java @@ -20,7 +20,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting; import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting; import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.EGLHelper; import org.dolphinemu.dolphinemu.utils.Log; @@ -473,7 +473,7 @@ public final class SettingsFragmentPresenter try { String shadersPath = - DirectoryInitializationService.getDolphinInternalDirectory() + "/Shaders"; + DirectoryInitialization.getDolphinInternalDirectory() + "/Shaders"; if (!TextUtils.isEmpty(subDir)) { shadersPath += "/" + subDir; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java index e86b68af69..23763ebee1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java @@ -11,7 +11,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.SettingSection; import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.BiMap; import org.dolphinemu.dolphinemu.utils.Log; @@ -462,7 +462,7 @@ public final class SettingsFile private static File getSettingsFile(String fileName) { return new File( - DirectoryInitializationService.getUserDirectory() + "/Config/" + fileName + ".ini"); + DirectoryInitialization.getUserDirectory() + "/Config/" + fileName + ".ini"); } private static File getGenericGameSettingsForAllRegions(String gameId) @@ -470,21 +470,21 @@ public final class SettingsFile // Use the first 3 chars from the gameId to load the generic game settings for all regions gameId = gameId.substring(0, 3); return new File( - DirectoryInitializationService.getDolphinInternalDirectory() + "/GameSettings/" + + DirectoryInitialization.getDolphinInternalDirectory() + "/GameSettings/" + gameId + ".ini"); } private static File getGenericGameSettingsFile(String gameId) { return new File( - DirectoryInitializationService.getDolphinInternalDirectory() + "/GameSettings/" + + DirectoryInitialization.getDolphinInternalDirectory() + "/GameSettings/" + gameId + ".ini"); } private static File getCustomGameSettingsFile(String gameId) { return new File( - DirectoryInitializationService.getUserDirectory() + "/GameSettings/" + gameId + ".ini"); + DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini"); } private static SettingSection sectionFromLine(String line, boolean isCustomGame) 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 68d0939da7..15d1ee8298 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 @@ -20,8 +20,8 @@ import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.overlay.InputOverlay; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState; import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver; import org.dolphinemu.dolphinemu.utils.Log; import org.dolphinemu.dolphinemu.utils.StartupHandler; @@ -127,7 +127,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C public void onResume() { super.onResume(); - if (DirectoryInitializationService.areDolphinDirectoriesReady()) + if (DirectoryInitialization.areDolphinDirectoriesReady()) { mEmulationState.run(activity.isActivityRecreated()); } @@ -160,7 +160,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 -> @@ -189,7 +189,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C LocalBroadcastManager.getInstance(getActivity()).registerReceiver( directoryStateReceiver, statusIntentFilter); - DirectoryInitializationService.startService(getActivity()); + DirectoryInitialization.start(getActivity()); } public void toggleInputOverlayVisibility() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java deleted file mode 100644 index 74e8a63fa4..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Copyright 2014 Dolphin Emulator Project - * Licensed under GPLv2+ - * Refer to the license.txt file included. - */ - -package org.dolphinemu.dolphinemu.services; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Environment; -import android.preference.PreferenceManager; -import android.support.v4.content.LocalBroadcastManager; - -import org.dolphinemu.dolphinemu.NativeLibrary; -import org.dolphinemu.dolphinemu.utils.Log; -import org.dolphinemu.dolphinemu.utils.PermissionsHandler; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -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 static final String BROADCAST_ACTION = - "org.dolphinemu.dolphinemu.DIRECTORY_INITIALIZATION"; - - public static final String EXTRA_STATE = "directoryState"; - private static volatile DirectoryInitializationState directoryState = null; - private static String userPath; - private static String internalPath; - private static AtomicBoolean isDolphinDirectoryInitializationRunning = new AtomicBoolean(false); - - public enum DirectoryInitializationState - { - DOLPHIN_DIRECTORIES_INITIALIZED, - EXTERNAL_STORAGE_PERMISSION_NEEDED, - CANT_FIND_EXTERNAL_STORAGE - } - - public DirectoryInitializationService() - { - // Superclass constructor is called to name the thread on which this service executes. - super("DirectoryInitializationService"); - } - - public static void startService(Context context) - { - Intent intent = new Intent(context, DirectoryInitializationService.class); - context.startService(intent); - } - - @Override - protected void onHandleIntent(Intent intent) - { - isDolphinDirectoryInitializationRunning.set(true); - - if (directoryState != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) - { - if (PermissionsHandler.hasWriteAccess(this)) - { - if (setDolphinUserDirectory()) - { - initializeInternalStorage(); - initializeExternalStorage(); - - 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() - { - if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) - { - File externalPath = Environment.getExternalStorageDirectory(); - if (externalPath != null) - { - userPath = externalPath.getAbsolutePath() + "/dolphin-emu"; - Log.debug("[DirectoryInitializationService] User Dir: " + userPath); - NativeLibrary.SetUserDirectory(userPath); - return true; - } - - } - - return false; - } - - private void initializeInternalStorage() - { - File sysDirectory = new File(getFilesDir(), "Sys"); - internalPath = sysDirectory.getAbsolutePath(); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - 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); - - SharedPreferences.Editor editor = preferences.edit(); - editor.putString("sysDirectoryVersion", revision); - editor.apply(); - } - - // Let the native code know where the Sys directory is. - SetSysDirectory(sysDirectory.getPath()); - } - - private void initializeExternalStorage() - { - // Create User directory structure and copy some NAND files from the extracted Sys directory. - CreateUserDirectories(); - - // GCPadNew.ini and WiimoteNew.ini must contain specific values in order for controller - // input to work as intended (they aren't user configurable), so we overwrite them just - // in case the user has tried to modify them manually. - // - // ...Except WiimoteNew.ini contains the user configurable settings for Wii Remote - // extensions in addition to all of its lines that aren't user configurable, so since we - // don't want to lose the selected extensions, we don't overwrite that file if it exists. - // - // TODO: Redo the Android controller system so that we don't have to extract these INIs. - String configDirectory = NativeLibrary.GetUserDirectory() + File.separator + "Config"; - copyAsset("GCPadNew.ini", new File(configDirectory, "GCPadNew.ini"), true); - copyAsset("WiimoteNew.ini", new File(configDirectory, "WiimoteNew.ini"), false); - } - - private static void deleteDirectoryRecursively(File file) - { - if (file.isDirectory()) - { - for (File child : file.listFiles()) - deleteDirectoryRecursively(child); - } - file.delete(); - } - - public static boolean areDolphinDirectoriesReady() - { - return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED; - } - - public static String getUserDirectory() - { - if (directoryState == null) - { - throw new IllegalStateException("DirectoryInitializationService has to run at least once!"); - } - else if (isDolphinDirectoryInitializationRunning.get()) - { - throw new IllegalStateException( - "DirectoryInitializationService has to finish running first!"); - } - return userPath; - - } - - public static String getDolphinInternalDirectory() - { - if (directoryState == null) - { - throw new IllegalStateException("DirectoryInitializationService has to run at least once!"); - } - else if (isDolphinDirectoryInitializationRunning.get()) - { - throw new IllegalStateException( - "DirectoryInitializationService has to finish running first!"); - } - return internalPath; - - } - - private void sendBroadcastState(DirectoryInitializationState state) - { - Intent localIntent = - new Intent(BROADCAST_ACTION) - .putExtra(EXTRA_STATE, state); - LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent); - } - - private void copyAsset(String asset, File output, Boolean overwrite) - { - Log.verbose("[DirectoryInitializationService] Copying File " + asset + " to " + output); - - try - { - if (!output.exists() || overwrite) - { - InputStream in = 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 + - e.getMessage()); - } - } - - private void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite) - { - Log.verbose("[DirectoryInitializationService] Copying Folder " + assetFolder + " to " + - outputFolder); - - try - { - boolean createdFolder = false; - for (String file : 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); - } - } - catch (IOException e) - { - Log.error("[DirectoryInitializationService] Failed to copy asset folder: " + assetFolder + - e.getMessage()); - } - } - - private void copyFile(InputStream in, OutputStream out) throws IOException - { - byte[] buffer = new byte[1024]; - int read; - - while ((read = in.read(buffer)) != -1) - { - out.write(buffer, 0, read); - } - } - - private static native void CreateUserDirectories(); - - private static native void SetSysDirectory(String path); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index ed95cab7d6..f5eee1bc68 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -19,7 +19,7 @@ import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView; @@ -188,7 +188,7 @@ public final class MainActivity extends AppCompatActivity implements MainView case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - DirectoryInitializationService.startService(this); + DirectoryInitialization.start(this); PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter( getSupportFragmentManager(), this); mViewPager.setAdapter(platformPagerAdapter); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index 6f73be35c9..f8b71eaed0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -22,7 +22,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.model.TvSettingsItem; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; @@ -158,7 +158,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView // Kicks off the program services to update all channels TvUtil.updateAllChannels(getApplicationContext()); - recreate(); + buildRowsAdapter(); } /** @@ -195,7 +195,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - DirectoryInitializationService.startService(this); + DirectoryInitialization.start(this); GameFileCacheService.startLoad(this); } else 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 e68852233c..6f3e07cbb5 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 @@ -15,7 +15,6 @@ import org.dolphinemu.dolphinemu.DolphinApplication; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; public class Analytics { @@ -36,16 +35,16 @@ public class Analytics SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); if (!preferences.getBoolean(analyticsAsked, false)) { - if (!DirectoryInitializationService.areDolphinDirectoriesReady()) + if (!DirectoryInitialization.areDolphinDirectoriesReady()) { // Wait for directories to get initialized IntentFilter statusIntentFilter = new IntentFilter( - DirectoryInitializationService.BROADCAST_ACTION); + DirectoryInitialization.BROADCAST_ACTION); directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState -> { if (directoryInitializationState == - DirectoryInitializationService.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) + DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) { LocalBroadcastManager.getInstance(context).unregisterReceiver(directoryStateReceiver); directoryStateReceiver = null; 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 new file mode 100644 index 0000000000..b0132c215f --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryInitialization.java @@ -0,0 +1,264 @@ +/** + * Copyright 2014 Dolphin Emulator Project + * Licensed under GPLv2+ + * Refer to the license.txt file included. + */ + +package org.dolphinemu.dolphinemu.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Environment; +import android.preference.PreferenceManager; +import android.support.v4.content.LocalBroadcastManager; + +import org.dolphinemu.dolphinemu.NativeLibrary; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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 DirectoryInitialization +{ + public static final String BROADCAST_ACTION = + "org.dolphinemu.dolphinemu.DIRECTORY_INITIALIZATION"; + + public static final String EXTRA_STATE = "directoryState"; + private static volatile DirectoryInitializationState directoryState = null; + private static String userPath; + private static String internalPath; + private static AtomicBoolean isDolphinDirectoryInitializationRunning = new AtomicBoolean(false); + + public enum DirectoryInitializationState + { + DOLPHIN_DIRECTORIES_INITIALIZED, + EXTERNAL_STORAGE_PERMISSION_NEEDED, + CANT_FIND_EXTERNAL_STORAGE + } + + 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(); + } + + 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); + initializeExternalStorage(context); + + 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 boolean setDolphinUserDirectory() + { + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) + { + File externalPath = Environment.getExternalStorageDirectory(); + if (externalPath != null) + { + userPath = externalPath.getAbsolutePath() + "/dolphin-emu"; + Log.debug("[DirectoryInitialization] User Dir: " + userPath); + NativeLibrary.SetUserDirectory(userPath); + return true; + } + + } + + return false; + } + + private static void initializeInternalStorage(Context context) + { + File sysDirectory = new File(context.getFilesDir(), "Sys"); + internalPath = sysDirectory.getAbsolutePath(); + + 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, context); + + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("sysDirectoryVersion", revision); + editor.apply(); + } + + // Let the native code know where the Sys directory is. + SetSysDirectory(sysDirectory.getPath()); + } + + private static void initializeExternalStorage(Context context) + { + // Create User directory structure and copy some NAND files from the extracted Sys directory. + CreateUserDirectories(); + + // GCPadNew.ini and WiimoteNew.ini must contain specific values in order for controller + // input to work as intended (they aren't user configurable), so we overwrite them just + // in case the user has tried to modify them manually. + // + // ...Except WiimoteNew.ini contains the user configurable settings for Wii Remote + // extensions in addition to all of its lines that aren't user configurable, so since we + // don't want to lose the selected extensions, we don't overwrite that file if it exists. + // + // TODO: Redo the Android controller system so that we don't have to extract these INIs. + String configDirectory = NativeLibrary.GetUserDirectory() + File.separator + "Config"; + copyAsset("GCPadNew.ini", new File(configDirectory, "GCPadNew.ini"), true, context); + copyAsset("WiimoteNew.ini", new File(configDirectory, "WiimoteNew.ini"), false, context); + } + + private static void deleteDirectoryRecursively(File file) + { + if (file.isDirectory()) + { + for (File child : file.listFiles()) + deleteDirectoryRecursively(child); + } + file.delete(); + } + + public static boolean areDolphinDirectoriesReady() + { + return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED; + } + + public static String getUserDirectory() + { + if (directoryState == null) + { + throw new IllegalStateException("DirectoryInitialization has to run at least once!"); + } + else if (isDolphinDirectoryInitializationRunning.get()) + { + throw new IllegalStateException( + "DirectoryInitialization has to finish running first!"); + } + return userPath; + + } + + public static String getDolphinInternalDirectory() + { + if (directoryState == null) + { + throw new IllegalStateException("DirectoryInitialization has to run at least once!"); + } + else if (isDolphinDirectoryInitializationRunning.get()) + { + throw new IllegalStateException( + "DirectoryInitialization has to finish running first!"); + } + return internalPath; + + } + + private static void sendBroadcastState(DirectoryInitializationState state, Context context) + { + Intent localIntent = + new Intent(BROADCAST_ACTION) + .putExtra(EXTRA_STATE, state); + LocalBroadcastManager.getInstance(context).sendBroadcast(localIntent); + } + + 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 = context.getAssets().open(asset); + OutputStream out = new FileOutputStream(output); + copyFile(in, out); + in.close(); + out.close(); + } + } + catch (IOException e) + { + Log.error("[DirectoryInitialization] Failed to copy asset file: " + asset + + e.getMessage()); + } + } + + 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 : context.getAssets().list(assetFolder)) + { + if (!createdFolder) + { + outputFolder.mkdir(); + createdFolder = true; + } + copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file), + overwrite, context); + copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite, + context); + } + } + catch (IOException e) + { + Log.error("[DirectoryInitialization] Failed to copy asset folder: " + assetFolder + + e.getMessage()); + } + } + + private static void copyFile(InputStream in, OutputStream out) throws IOException + { + byte[] buffer = new byte[1024]; + int read; + + while ((read = in.read(buffer)) != -1) + { + out.write(buffer, 0, read); + } + } + + private static native void CreateUserDirectories(); + + private static native void SetSysDirectory(String path); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryStateReceiver.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryStateReceiver.java index 2471e50a32..fe51b41c3f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryStateReceiver.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/DirectoryStateReceiver.java @@ -4,8 +4,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService; -import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState; +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState; import rx.functions.Action1; @@ -22,7 +21,7 @@ public class DirectoryStateReceiver extends BroadcastReceiver 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/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index a160786063..089d0b776d 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -246,8 +246,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadStateAs( jobject obj, jstring path); JNIEXPORT void JNICALL -Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_CreateUserDirectories( - JNIEnv* env, jobject obj); +Java_org_dolphinemu_dolphinemu_utils_DirectoryInitialization_CreateUserDirectories(JNIEnv* env, + jobject obj); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirectory( JNIEnv* env, jobject obj, jstring jDirectory); JNIEXPORT jstring JNICALL @@ -457,8 +457,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadStateAs( State::LoadAs(GetJString(env, path)); } -JNIEXPORT void JNICALL -Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_SetSysDirectory( +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_DirectoryInitialization_SetSysDirectory( JNIEnv* env, jobject obj, jstring jPath) { const std::string path = GetJString(env, jPath); @@ -466,8 +465,8 @@ Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_SetSysDir } JNIEXPORT void JNICALL -Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_CreateUserDirectories( - JNIEnv* env, jobject obj) +Java_org_dolphinemu_dolphinemu_utils_DirectoryInitialization_CreateUserDirectories(JNIEnv* env, + jobject obj) { UICommon::CreateDirectories(); } -- cgit v1.2.3