From 164619790276ce38312858e5b76a5b5223e775a8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 12 Aug 2022 21:29:54 +0200 Subject: [PATCH] Android: Force quit app if external storage isn't mounted In the past, directory initialization could fail for two reasons: The user was rejecting the storage permission, or external storage wasn't mounted. With the introduction of scoped storage, the first of these two couldn't happen anymore; if the user rejects the storage permission, we just use the app-specific directory instead of the dolphin-emu directory. By making it so Dolphin force quits if external storage isn't mounted, we can get rid of our code for handling retrying directory initialization after it fails. I think this slight hit to UX is worth it considering that basically nobody has an Android device with detachable primary external storage anymore. And the UX hit is very small; the user just has to manually open the app again after remounting external storage. The toast about external storage not being mounted will still be displayed. The recent merge of the splash screen PR may have made it so that the code for handling directory initialization failing doesn't work anymore. To be completely honest, I'm not sure how to even test this in 2022. --- .../activities/AppLinkActivity.java | 2 +- .../activities/EmulationActivity.java | 2 +- .../ui/SettingsActivityPresenter.java | 15 +--- .../settings/ui/SettingsActivityView.java | 2 +- .../services/GameFileCacheManager.java | 4 +- .../dolphinemu/ui/main/MainActivity.java | 6 +- .../dolphinemu/ui/main/MainPresenter.java | 14 ++-- .../AfterDirectoryInitializationRunner.java | 82 ++----------------- .../dolphinemu/utils/Analytics.java | 2 +- .../utils/DirectoryInitialization.java | 52 ++++++------ .../dolphinemu/utils/StartupHandler.java | 2 +- 11 files changed, 53 insertions(+), 130 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java index 8230828106..2699322a96 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java @@ -63,7 +63,7 @@ public class AppLinkActivity extends FragmentActivity private void initResources() { mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner(); - mAfterDirectoryInitializationRunner.runWithLifecycle(this, true, () -> tryPlay(playAction)); + mAfterDirectoryInitializationRunner.runWithLifecycle(this, () -> tryPlay(playAction)); GameFileCacheManager.isLoading().observe(this, (isLoading) -> { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index 47749d78e8..89bb84f75e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -185,7 +185,7 @@ public final class EmulationActivity extends AppCompatActivity private static void performLaunchChecks(FragmentActivity activity, Runnable continueCallback) { - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, () -> + new AfterDirectoryInitializationRunner().runWithLifecycle(activity, () -> { if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DEFAULT_ISO) || !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_FS_PATH) || 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 4135f06e01..710342a64e 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 @@ -63,6 +63,8 @@ public final class SettingsActivityPresenter private void loadSettingsUI() { + mView.hideLoading(); + if (mSettings.isEmpty()) { if (!TextUtils.isEmpty(mGameId)) @@ -86,18 +88,9 @@ public final class SettingsActivityPresenter private void prepareDolphinDirectoriesIfNeeded() { - if (DirectoryInitialization.areDolphinDirectoriesReady()) - { - loadSettingsUI(); - } - else - { - mView.showLoading(); + mView.showLoading(); - new AfterDirectoryInitializationRunner() - .setFinishedCallback(mView::hideLoading) - .runWithLifecycle(mActivity, true, this::loadSettingsUI); - } + new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, this::loadSettingsUI); } public Settings getSettings() 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 d126920c24..b1b5ec8bd6 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 @@ -100,7 +100,7 @@ public interface SettingsActivityView void showLoading(); /** - * Hide the loading the dialog + * Hide the loading dialog */ void hideLoading(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java index 2364a3800c..f53e3de56f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java @@ -128,7 +128,7 @@ public final class GameFileCacheManager if (!loadInProgress.getValue()) { loadInProgress.setValue(true); - new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, + new AfterDirectoryInitializationRunner().runWithoutLifecycle( () -> executor.execute(GameFileCacheManager::load)); } } @@ -144,7 +144,7 @@ public final class GameFileCacheManager if (!rescanInProgress.getValue()) { rescanInProgress.setValue(true); - new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, + new AfterDirectoryInitializationRunner().runWithoutLifecycle( () -> executor.execute(GameFileCacheManager::rescan)); } } 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 c0238617f9..4954a4cf90 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 @@ -79,7 +79,7 @@ public final class MainActivity extends AppCompatActivity if (!DirectoryInitialization.isWaitingForWriteAccess(this)) { new AfterDirectoryInitializationRunner() - .runWithLifecycle(this, false, this::setPlatformTabsAndStartGameFileCacheService); + .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService); } } @@ -92,7 +92,7 @@ public final class MainActivity extends AppCompatActivity { DirectoryInitialization.start(this); new AfterDirectoryInitializationRunner() - .runWithLifecycle(this, false, this::setPlatformTabsAndStartGameFileCacheService); + .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService); } mPresenter.onResume(); @@ -268,7 +268,7 @@ public final class MainActivity extends AppCompatActivity DirectoryInitialization.start(this); new AfterDirectoryInitializationRunner() - .runWithLifecycle(this, false, this::setPlatformTabsAndStartGameFileCacheService); + .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService); } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java index e012cd84dc..b34ffff66c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java @@ -81,7 +81,7 @@ public final class MainPresenter public void onFabClick() { - new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, true, + new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, mView::launchFileListActivity); } @@ -99,7 +99,7 @@ public final class MainPresenter return true; case R.id.button_add_directory: - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, + new AfterDirectoryInitializationRunner().runWithLifecycle(activity, mView::launchFileListActivity); return true; @@ -112,22 +112,22 @@ public final class MainPresenter return true; case R.id.menu_online_system_update: - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, + new AfterDirectoryInitializationRunner().runWithLifecycle(activity, this::launchOnlineUpdate); return true; case R.id.menu_install_wad: - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, + new AfterDirectoryInitializationRunner().runWithLifecycle(activity, () -> mView.launchOpenFileActivity(REQUEST_WAD_FILE)); return true; case R.id.menu_import_wii_save: - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, + new AfterDirectoryInitializationRunner().runWithLifecycle(activity, () -> mView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE)); return true; case R.id.menu_import_nand_backup: - new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, + new AfterDirectoryInitializationRunner().runWithLifecycle(activity, () -> mView.launchOpenFileActivity(REQUEST_NAND_BIN_FILE)); return true; } @@ -325,7 +325,7 @@ public final class MainPresenter } else { - new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, true, () -> + new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, () -> { SystemMenuNotInstalledDialogFragment dialogFragment = new SystemMenuNotInstalledDialogFragment(); 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 index d16e26d447..651512bcb4 100644 --- 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 @@ -2,43 +2,14 @@ package org.dolphinemu.dolphinemu.utils; -import android.content.Context; -import android.widget.Toast; - import androidx.core.app.ComponentActivity; import androidx.lifecycle.Observer; -import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState; public class AfterDirectoryInitializationRunner { private Observer mObserver; - private Runnable mUnregisterCallback; - - /** - * Sets a Runnable which will be called when: - * - * 1. The Runnable supplied to {@link #runWithLifecycle}/{@link #runWithoutLifecycle} - * is just about to run, or - * 2. {@link #runWithLifecycle}/{@link #runWithoutLifecycle} was called with - * abortOnFailure == true and there is a failure - * - * @return this - */ - public AfterDirectoryInitializationRunner setFinishedCallback(Runnable runnable) - { - mUnregisterCallback = runnable; - return this; - } - - private void runFinishedCallback() - { - if (mUnregisterCallback != null) - { - mUnregisterCallback.run(); - } - } /** * Executes a Runnable after directory initialization has finished. @@ -59,23 +30,15 @@ public class AfterDirectoryInitializationRunner * If the passed-in activity gets destroyed before this operation finishes, * it will be automatically canceled. */ - public void runWithLifecycle(ComponentActivity activity, boolean abortOnFailure, - Runnable runnable) + public void runWithLifecycle(ComponentActivity activity, Runnable runnable) { if (DirectoryInitialization.areDolphinDirectoriesReady()) { - runFinishedCallback(); runnable.run(); } - else if (abortOnFailure && - showErrorMessage(activity, - DirectoryInitialization.getDolphinDirectoriesState().getValue())) - { - runFinishedCallback(); - } else { - mObserver = createObserver(activity, abortOnFailure, runnable); + mObserver = createObserver(runnable); DirectoryInitialization.getDolphinDirectoriesState().observe(activity, mObserver); } } @@ -96,46 +59,26 @@ public class AfterDirectoryInitializationRunner * the attempt to run the Runnable will never be aborted, and the Runnable * is guaranteed to run if directory initialization ever finishes. */ - public void runWithoutLifecycle(Context context, boolean abortOnFailure, Runnable runnable) + public void runWithoutLifecycle(Runnable runnable) { if (DirectoryInitialization.areDolphinDirectoriesReady()) { - runFinishedCallback(); runnable.run(); } - else if (abortOnFailure && - showErrorMessage(context, - DirectoryInitialization.getDolphinDirectoriesState().getValue())) - { - runFinishedCallback(); - } else { - mObserver = createObserver(context, abortOnFailure, runnable); + mObserver = createObserver(runnable); DirectoryInitialization.getDolphinDirectoriesState().observeForever(mObserver); } } - private Observer createObserver(Context context, - boolean abortOnFailure, Runnable runnable) + private Observer createObserver(Runnable runnable) { return (state) -> { - boolean done = state == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED; - - if (!done && abortOnFailure) - { - done = showErrorMessage(context, state); - } - - if (done) - { - cancel(); - runFinishedCallback(); - } - if (state == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) { + cancel(); runnable.run(); } }; @@ -145,17 +88,4 @@ public class AfterDirectoryInitializationRunner { DirectoryInitialization.getDolphinDirectoriesState().removeObserver(mObserver); } - - private static boolean showErrorMessage(Context context, DirectoryInitializationState state) - { - switch (state) - { - case CANT_FIND_EXTERNAL_STORAGE: - Toast.makeText(context, R.string.external_storage_not_mounted, Toast.LENGTH_LONG).show(); - return true; - - default: - return false; - } - } } 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 b5c5548ca3..a39a52267b 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 @@ -25,7 +25,7 @@ public class Analytics public static void checkAnalyticsInit(Context context) { - new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, () -> + new AfterDirectoryInitializationRunner().runWithoutLifecycle(() -> { if (!BooleanSetting.MAIN_ANALYTICS_PERMISSION_ASKED.getBooleanGlobal()) { 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 56ade14623..6f8ec2c805 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 @@ -10,6 +10,7 @@ import android.content.SharedPreferences; import android.os.Build; import android.os.Environment; import android.preference.PreferenceManager; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,6 +18,7 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import org.dolphinemu.dolphinemu.NativeLibrary; +import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.activities.EmulationActivity; import java.io.File; @@ -43,13 +45,12 @@ public final class DirectoryInitialization { NOT_YET_INITIALIZED, INITIALIZING, - DOLPHIN_DIRECTORIES_INITIALIZED, - CANT_FIND_EXTERNAL_STORAGE + DOLPHIN_DIRECTORIES_INITIALIZED } public static void start(Context context) { - if (directoryState.getValue() == DirectoryInitializationState.INITIALIZING) + if (directoryState.getValue() != DirectoryInitializationState.NOT_YET_INITIALIZED) return; directoryState.setValue(DirectoryInitializationState.INITIALIZING); @@ -60,31 +61,30 @@ public final class DirectoryInitialization private static void init(Context context) { - if (directoryState.getValue() != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) + if (directoryState.getValue() == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) + return; + + if (!setDolphinUserDirectory(context)) { - if (setDolphinUserDirectory(context)) - { - initializeInternalStorage(context); - boolean wiimoteIniWritten = initializeExternalStorage(context); - NativeLibrary.Initialize(); - NativeLibrary.ReportStartToAnalytics(); - - areDirectoriesAvailable = true; - - if (wiimoteIniWritten) - { - // This has to be done after calling NativeLibrary.Initialize(), - // as it relies on the config system - EmulationActivity.updateWiimoteNewIniPreferences(context); - } - - directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED); - } - else - { - directoryState.postValue(DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE); - } + Toast.makeText(context, R.string.external_storage_not_mounted, Toast.LENGTH_LONG).show(); + System.exit(1); } + + initializeInternalStorage(context); + boolean wiimoteIniWritten = initializeExternalStorage(context); + NativeLibrary.Initialize(); + NativeLibrary.ReportStartToAnalytics(); + + areDirectoriesAvailable = true; + + if (wiimoteIniWritten) + { + // This has to be done after calling NativeLibrary.Initialize(), + // as it relies on the config system + EmulationActivity.updateWiimoteNewIniPreferences(context); + } + + directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED); } @Nullable 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 ba4062bb88..918a6859c3 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 @@ -112,7 +112,7 @@ public final class StartupHandler final Instant lastOpened = Instant.ofEpochMilli(lastOpen); if (current.isAfter(lastOpened.plus(6, ChronoUnit.HOURS))) { - new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, + new AfterDirectoryInitializationRunner().runWithoutLifecycle( NativeLibrary::ReportStartToAnalytics); } }