Merge pull request #10980 from JosJuice/android-no-dir-init-fail

Android: Force quit app if external storage isn't mounted
This commit is contained in:
JosJuice 2022-08-14 15:47:50 +02:00 committed by GitHub
commit b6a18b0da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 53 additions and 130 deletions

View File

@ -63,7 +63,7 @@ public class AppLinkActivity extends FragmentActivity
private void initResources() private void initResources()
{ {
mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner(); mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner();
mAfterDirectoryInitializationRunner.runWithLifecycle(this, true, () -> tryPlay(playAction)); mAfterDirectoryInitializationRunner.runWithLifecycle(this, () -> tryPlay(playAction));
GameFileCacheManager.isLoading().observe(this, (isLoading) -> GameFileCacheManager.isLoading().observe(this, (isLoading) ->
{ {

View File

@ -185,7 +185,7 @@ public final class EmulationActivity extends AppCompatActivity
private static void performLaunchChecks(FragmentActivity activity, private static void performLaunchChecks(FragmentActivity activity,
Runnable continueCallback) Runnable continueCallback)
{ {
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, () -> new AfterDirectoryInitializationRunner().runWithLifecycle(activity, () ->
{ {
if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DEFAULT_ISO) || if (!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DEFAULT_ISO) ||
!FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_FS_PATH) || !FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_FS_PATH) ||

View File

@ -63,6 +63,8 @@ public final class SettingsActivityPresenter
private void loadSettingsUI() private void loadSettingsUI()
{ {
mView.hideLoading();
if (mSettings.isEmpty()) if (mSettings.isEmpty())
{ {
if (!TextUtils.isEmpty(mGameId)) if (!TextUtils.isEmpty(mGameId))
@ -85,19 +87,10 @@ public final class SettingsActivityPresenter
} }
private void prepareDolphinDirectoriesIfNeeded() private void prepareDolphinDirectoriesIfNeeded()
{
if (DirectoryInitialization.areDolphinDirectoriesReady())
{
loadSettingsUI();
}
else
{ {
mView.showLoading(); mView.showLoading();
new AfterDirectoryInitializationRunner() new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, this::loadSettingsUI);
.setFinishedCallback(mView::hideLoading)
.runWithLifecycle(mActivity, true, this::loadSettingsUI);
}
} }
public Settings getSettings() public Settings getSettings()

View File

@ -100,7 +100,7 @@ public interface SettingsActivityView
void showLoading(); void showLoading();
/** /**
* Hide the loading the dialog * Hide the loading dialog
*/ */
void hideLoading(); void hideLoading();

View File

@ -128,7 +128,7 @@ public final class GameFileCacheManager
if (!loadInProgress.getValue()) if (!loadInProgress.getValue())
{ {
loadInProgress.setValue(true); loadInProgress.setValue(true);
new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, new AfterDirectoryInitializationRunner().runWithoutLifecycle(
() -> executor.execute(GameFileCacheManager::load)); () -> executor.execute(GameFileCacheManager::load));
} }
} }
@ -144,7 +144,7 @@ public final class GameFileCacheManager
if (!rescanInProgress.getValue()) if (!rescanInProgress.getValue())
{ {
rescanInProgress.setValue(true); rescanInProgress.setValue(true);
new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, new AfterDirectoryInitializationRunner().runWithoutLifecycle(
() -> executor.execute(GameFileCacheManager::rescan)); () -> executor.execute(GameFileCacheManager::rescan));
} }
} }

View File

@ -79,7 +79,7 @@ public final class MainActivity extends AppCompatActivity
if (!DirectoryInitialization.isWaitingForWriteAccess(this)) if (!DirectoryInitialization.isWaitingForWriteAccess(this))
{ {
new AfterDirectoryInitializationRunner() new AfterDirectoryInitializationRunner()
.runWithLifecycle(this, false, this::setPlatformTabsAndStartGameFileCacheService); .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService);
} }
} }
@ -92,7 +92,7 @@ public final class MainActivity extends AppCompatActivity
{ {
DirectoryInitialization.start(this); DirectoryInitialization.start(this);
new AfterDirectoryInitializationRunner() new AfterDirectoryInitializationRunner()
.runWithLifecycle(this, false, this::setPlatformTabsAndStartGameFileCacheService); .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService);
} }
mPresenter.onResume(); mPresenter.onResume();
@ -268,7 +268,7 @@ public final class MainActivity extends AppCompatActivity
DirectoryInitialization.start(this); DirectoryInitialization.start(this);
new AfterDirectoryInitializationRunner() new AfterDirectoryInitializationRunner()
.runWithLifecycle(this, false, this::setPlatformTabsAndStartGameFileCacheService); .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService);
} }
} }

View File

@ -81,7 +81,7 @@ public final class MainPresenter
public void onFabClick() public void onFabClick()
{ {
new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, true, new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity,
mView::launchFileListActivity); mView::launchFileListActivity);
} }
@ -99,7 +99,7 @@ public final class MainPresenter
return true; return true;
case R.id.button_add_directory: case R.id.button_add_directory:
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
mView::launchFileListActivity); mView::launchFileListActivity);
return true; return true;
@ -112,22 +112,22 @@ public final class MainPresenter
return true; return true;
case R.id.menu_online_system_update: case R.id.menu_online_system_update:
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
this::launchOnlineUpdate); this::launchOnlineUpdate);
return true; return true;
case R.id.menu_install_wad: case R.id.menu_install_wad:
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
() -> mView.launchOpenFileActivity(REQUEST_WAD_FILE)); () -> mView.launchOpenFileActivity(REQUEST_WAD_FILE));
return true; return true;
case R.id.menu_import_wii_save: case R.id.menu_import_wii_save:
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
() -> mView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE)); () -> mView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE));
return true; return true;
case R.id.menu_import_nand_backup: case R.id.menu_import_nand_backup:
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
() -> mView.launchOpenFileActivity(REQUEST_NAND_BIN_FILE)); () -> mView.launchOpenFileActivity(REQUEST_NAND_BIN_FILE));
return true; return true;
} }
@ -325,7 +325,7 @@ public final class MainPresenter
} }
else else
{ {
new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, true, () -> new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, () ->
{ {
SystemMenuNotInstalledDialogFragment dialogFragment = SystemMenuNotInstalledDialogFragment dialogFragment =
new SystemMenuNotInstalledDialogFragment(); new SystemMenuNotInstalledDialogFragment();

View File

@ -2,43 +2,14 @@
package org.dolphinemu.dolphinemu.utils; package org.dolphinemu.dolphinemu.utils;
import android.content.Context;
import android.widget.Toast;
import androidx.core.app.ComponentActivity; import androidx.core.app.ComponentActivity;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState;
public class AfterDirectoryInitializationRunner public class AfterDirectoryInitializationRunner
{ {
private Observer<DirectoryInitializationState> mObserver; private Observer<DirectoryInitializationState> 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. * 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, * If the passed-in activity gets destroyed before this operation finishes,
* it will be automatically canceled. * it will be automatically canceled.
*/ */
public void runWithLifecycle(ComponentActivity activity, boolean abortOnFailure, public void runWithLifecycle(ComponentActivity activity, Runnable runnable)
Runnable runnable)
{ {
if (DirectoryInitialization.areDolphinDirectoriesReady()) if (DirectoryInitialization.areDolphinDirectoriesReady())
{ {
runFinishedCallback();
runnable.run(); runnable.run();
} }
else if (abortOnFailure &&
showErrorMessage(activity,
DirectoryInitialization.getDolphinDirectoriesState().getValue()))
{
runFinishedCallback();
}
else else
{ {
mObserver = createObserver(activity, abortOnFailure, runnable); mObserver = createObserver(runnable);
DirectoryInitialization.getDolphinDirectoriesState().observe(activity, mObserver); 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 * the attempt to run the Runnable will never be aborted, and the Runnable
* is guaranteed to run if directory initialization ever finishes. * 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()) if (DirectoryInitialization.areDolphinDirectoriesReady())
{ {
runFinishedCallback();
runnable.run(); runnable.run();
} }
else if (abortOnFailure &&
showErrorMessage(context,
DirectoryInitialization.getDolphinDirectoriesState().getValue()))
{
runFinishedCallback();
}
else else
{ {
mObserver = createObserver(context, abortOnFailure, runnable); mObserver = createObserver(runnable);
DirectoryInitialization.getDolphinDirectoriesState().observeForever(mObserver); DirectoryInitialization.getDolphinDirectoriesState().observeForever(mObserver);
} }
} }
private Observer<DirectoryInitializationState> createObserver(Context context, private Observer<DirectoryInitializationState> createObserver(Runnable runnable)
boolean abortOnFailure, Runnable runnable)
{ {
return (state) -> 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) if (state == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
{ {
cancel();
runnable.run(); runnable.run();
} }
}; };
@ -145,17 +88,4 @@ public class AfterDirectoryInitializationRunner
{ {
DirectoryInitialization.getDolphinDirectoriesState().removeObserver(mObserver); 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;
}
}
} }

View File

@ -25,7 +25,7 @@ public class Analytics
public static void checkAnalyticsInit(Context context) public static void checkAnalyticsInit(Context context)
{ {
new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, () -> new AfterDirectoryInitializationRunner().runWithoutLifecycle(() ->
{ {
if (!BooleanSetting.MAIN_ANALYTICS_PERMISSION_ASKED.getBooleanGlobal()) if (!BooleanSetting.MAIN_ANALYTICS_PERMISSION_ASKED.getBooleanGlobal())
{ {

View File

@ -10,6 +10,7 @@ import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -17,6 +18,7 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import java.io.File; import java.io.File;
@ -43,13 +45,12 @@ public final class DirectoryInitialization
{ {
NOT_YET_INITIALIZED, NOT_YET_INITIALIZED,
INITIALIZING, INITIALIZING,
DOLPHIN_DIRECTORIES_INITIALIZED, DOLPHIN_DIRECTORIES_INITIALIZED
CANT_FIND_EXTERNAL_STORAGE
} }
public static void start(Context context) public static void start(Context context)
{ {
if (directoryState.getValue() == DirectoryInitializationState.INITIALIZING) if (directoryState.getValue() != DirectoryInitializationState.NOT_YET_INITIALIZED)
return; return;
directoryState.setValue(DirectoryInitializationState.INITIALIZING); directoryState.setValue(DirectoryInitializationState.INITIALIZING);
@ -60,10 +61,15 @@ public final class DirectoryInitialization
private static void init(Context context) 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))
{ {
Toast.makeText(context, R.string.external_storage_not_mounted, Toast.LENGTH_LONG).show();
System.exit(1);
}
initializeInternalStorage(context); initializeInternalStorage(context);
boolean wiimoteIniWritten = initializeExternalStorage(context); boolean wiimoteIniWritten = initializeExternalStorage(context);
NativeLibrary.Initialize(); NativeLibrary.Initialize();
@ -80,12 +86,6 @@ public final class DirectoryInitialization
directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED); directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
} }
else
{
directoryState.postValue(DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE);
}
}
}
@Nullable @Nullable
private static File getLegacyUserDirectoryPath() private static File getLegacyUserDirectoryPath()

View File

@ -112,7 +112,7 @@ public final class StartupHandler
final Instant lastOpened = Instant.ofEpochMilli(lastOpen); final Instant lastOpened = Instant.ofEpochMilli(lastOpen);
if (current.isAfter(lastOpened.plus(6, ChronoUnit.HOURS))) if (current.isAfter(lastOpened.plus(6, ChronoUnit.HOURS)))
{ {
new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false, new AfterDirectoryInitializationRunner().runWithoutLifecycle(
NativeLibrary::ReportStartToAnalytics); NativeLibrary::ReportStartToAnalytics);
} }
} }