Merge pull request #10231 from JosJuice/android-dir-init-livedata

Android: Make DirectoryInitialization use LiveData
This commit is contained in:
Mai M 2021-11-21 21:07:17 -05:00 committed by GitHub
commit 15ff70baba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 130 additions and 132 deletions

View File

@ -63,7 +63,7 @@ public class AppLinkActivity extends FragmentActivity
private void initResources()
{
mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner();
mAfterDirectoryInitializationRunner.run(this, true, () -> tryPlay(playAction));
mAfterDirectoryInitializationRunner.runWithLifecycle(this, true, () -> tryPlay(playAction));
GameFileCacheManager.isLoading().observe(this, (isLoading) ->
{
@ -122,11 +122,7 @@ public class AppLinkActivity extends FragmentActivity
private void startGame(GameFile game)
{
if (mAfterDirectoryInitializationRunner != null)
{
mAfterDirectoryInitializationRunner.cancel();
mAfterDirectoryInitializationRunner = null;
}
mAfterDirectoryInitializationRunner.cancel();
EmulationActivity.launch(this, GameFileCacheManager.findSecondDiscAndGetPaths(game), false);
}
}

View File

@ -176,7 +176,7 @@ public final class EmulationActivity extends AppCompatActivity
if (sIgnoreLaunchRequests)
return;
new AfterDirectoryInitializationRunner().run(activity, true, () ->
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true, () ->
{
if (FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_DEFAULT_ISO) &&
FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_FS_PATH) &&

View File

@ -74,8 +74,7 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
MenuTag menuTag = (MenuTag) launcher.getSerializableExtra(ARG_MENU_TAG);
mPresenter = new SettingsActivityPresenter(this, getSettings());
mPresenter.onCreate(savedInstanceState, menuTag, gameID, revision, isWii,
getApplicationContext());
mPresenter.onCreate(savedInstanceState, menuTag, gameID, revision, isWii, this);
}
@Override

View File

@ -2,10 +2,11 @@
package org.dolphinemu.dolphinemu.features.settings.ui;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.core.app.ComponentActivity;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
@ -22,13 +23,11 @@ public final class SettingsActivityPresenter
private boolean mShouldSave;
private AfterDirectoryInitializationRunner mAfterDirectoryInitializationRunner;
private MenuTag mMenuTag;
private String mGameId;
private int mRevision;
private boolean mIsWii;
private Context mContext;
private ComponentActivity mActivity;
SettingsActivityPresenter(SettingsActivityView view, Settings settings)
{
@ -37,13 +36,13 @@ public final class SettingsActivityPresenter
}
public void onCreate(Bundle savedInstanceState, MenuTag menuTag, String gameId, int revision,
boolean isWii, Context context)
boolean isWii, ComponentActivity activity)
{
this.mMenuTag = menuTag;
this.mGameId = gameId;
this.mRevision = revision;
this.mIsWii = isWii;
this.mContext = context;
this.mActivity = activity;
mShouldSave = savedInstanceState != null && savedInstanceState.getBoolean(KEY_SHOULD_SAVE);
}
@ -95,9 +94,9 @@ public final class SettingsActivityPresenter
{
mView.showLoading();
mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner();
mAfterDirectoryInitializationRunner.setFinishedCallback(mView::hideLoading);
mAfterDirectoryInitializationRunner.run(mContext, true, this::loadSettingsUI);
new AfterDirectoryInitializationRunner()
.setFinishedCallback(mView::hideLoading)
.runWithLifecycle(mActivity, true, this::loadSettingsUI);
}
}
@ -114,16 +113,10 @@ public final class SettingsActivityPresenter
public void onStop(boolean finishing)
{
if (mAfterDirectoryInitializationRunner != null)
{
mAfterDirectoryInitializationRunner.cancel();
mAfterDirectoryInitializationRunner = null;
}
if (mSettings != null && finishing && mShouldSave)
{
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");
mSettings.saveSettings(mView, mContext);
mSettings.saveSettings(mView, mActivity);
}
}
@ -172,7 +165,7 @@ public final class SettingsActivityPresenter
break;
case 2:
mView.showToastMessage(mContext.getString(R.string.make_sure_continuous_scan_enabled));
mView.showToastMessage(mActivity.getString(R.string.make_sure_continuous_scan_enabled));
break;
}
}

View File

@ -26,6 +26,7 @@ public final class GameFileCacheManager
private static GameFileCache gameFileCache = null;
private static final MutableLiveData<GameFile[]> gameFiles =
new MutableLiveData<>(new GameFile[]{});
private static boolean runRescanAfterLoad = false;
private static final ExecutorService executor = Executors.newFixedThreadPool(1);
private static final MutableLiveData<Boolean> loadInProgress = new MutableLiveData<>(false);
@ -127,7 +128,7 @@ public final class GameFileCacheManager
if (!loadInProgress.getValue())
{
loadInProgress.setValue(true);
new AfterDirectoryInitializationRunner().run(context, false,
new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false,
() -> executor.execute(GameFileCacheManager::load));
}
}
@ -135,14 +136,15 @@ public final class GameFileCacheManager
/**
* Asynchronously scans for games in the user's configured folders,
* updating the game file cache with the results.
* If startLoad hasn't been called before this, this has no effect.
* If loading the game file cache hasn't started or hasn't finished,
* the execution of this will be postponed until it finishes.
*/
public static void startRescan(Context context)
{
if (!rescanInProgress.getValue())
{
rescanInProgress.setValue(true);
new AfterDirectoryInitializationRunner().run(context, false,
new AfterDirectoryInitializationRunner().runWithoutLifecycle(context, false,
() -> executor.execute(GameFileCacheManager::rescan));
}
}
@ -190,17 +192,33 @@ public final class GameFileCacheManager
}
}
if (runRescanAfterLoad)
{
rescanInProgress.postValue(true);
}
loadInProgress.postValue(false);
if (runRescanAfterLoad)
{
runRescanAfterLoad = false;
rescan();
}
}
/**
* Scans for games in the user's configured folders,
* updating the game file cache with the results.
* If load hasn't been called before this, this has no effect.
* If load hasn't been called before this, the execution of this
* will be postponed until after load runs.
*/
private static void rescan()
{
if (gameFileCache != null)
if (gameFileCache == null)
{
runRescanAfterLoad = true;
}
else
{
String[] gamePaths = GameFileCache.getAllGamePaths();

View File

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

View File

@ -73,7 +73,7 @@ public final class MainPresenter
mView.launchFileListActivity();
}
public boolean handleOptionSelection(int itemId, Context context)
public boolean handleOptionSelection(int itemId, ComponentActivity activity)
{
switch (itemId)
{
@ -83,7 +83,7 @@ public final class MainPresenter
case R.id.menu_refresh:
mView.setRefreshing(true);
GameFileCacheManager.startRescan(context);
GameFileCacheManager.startRescan(activity);
return true;
case R.id.button_add_directory:
@ -95,17 +95,17 @@ public final class MainPresenter
return true;
case R.id.menu_install_wad:
new AfterDirectoryInitializationRunner().run(context, true,
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true,
() -> mView.launchOpenFileActivity(REQUEST_WAD_FILE));
return true;
case R.id.menu_import_wii_save:
new AfterDirectoryInitializationRunner().run(context, true,
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true,
() -> mView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE));
return true;
case R.id.menu_import_nand_backup:
new AfterDirectoryInitializationRunner().run(context, true,
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true,
() -> mView.launchOpenFileActivity(REQUEST_NAND_BIN_FILE));
return true;
}
@ -121,12 +121,9 @@ public final class MainPresenter
mDirToAdd = null;
}
if (sShouldRescanLibrary && !GameFileCacheManager.isRescanning().getValue())
if (sShouldRescanLibrary)
{
new AfterDirectoryInitializationRunner().run(mActivity, false, () ->
{
GameFileCacheManager.startRescan(mActivity);
});
GameFileCacheManager.startRescan(mActivity);
}
sShouldRescanLibrary = true;

View File

@ -3,30 +3,33 @@
package org.dolphinemu.dolphinemu.utils;
import android.content.Context;
import android.content.IntentFilter;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
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 DirectoryStateReceiver mDirectoryStateReceiver;
private LocalBroadcastManager mLocalBroadcastManager;
private Observer<DirectoryInitializationState> mObserver;
private Runnable mUnregisterCallback;
/**
* Sets a Runnable which will be called when:
*
* 1. The Runnable supplied to {@link #run} is just about to run, or
* 2. {@link #run} was called with abortOnFailure == true and there is a failure
* 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 void setFinishedCallback(Runnable runnable)
public AfterDirectoryInitializationRunner setFinishedCallback(Runnable runnable)
{
mUnregisterCallback = runnable;
return this;
}
private void runFinishedCallback()
@ -48,13 +51,16 @@ public class AfterDirectoryInitializationRunner
*
* Calling this function multiple times per object is not supported.
*
* If abortOnFailure is true and the user has not granted the required
* permission or the external storage was not found, a message will be
* shown to the user and the Runnable will not run. If it is false, the
* attempt to run the Runnable will never be aborted, and the Runnable
* If abortOnFailure is true and external storage was not found, a message
* will be shown to the user and the Runnable will not run. If it is false,
* the attempt to run the Runnable will never be aborted, and the Runnable
* is guaranteed to run if directory initialization ever finishes.
*
* If the passed-in activity gets destroyed before this operation finishes,
* it will be automatically canceled.
*/
public void run(Context context, boolean abortOnFailure, Runnable runnable)
public void runWithLifecycle(ComponentActivity activity, boolean abortOnFailure,
Runnable runnable)
{
if (DirectoryInitialization.areDolphinDirectoriesReady())
{
@ -62,19 +68,58 @@ public class AfterDirectoryInitializationRunner
runnable.run();
}
else if (abortOnFailure &&
showErrorMessage(context, DirectoryInitialization.getDolphinDirectoriesState()))
showErrorMessage(activity,
DirectoryInitialization.getDolphinDirectoriesState().getValue()))
{
runFinishedCallback();
}
else
{
runAfterInitialization(context, abortOnFailure, runnable);
mObserver = createObserver(activity, abortOnFailure, runnable);
DirectoryInitialization.getDolphinDirectoriesState().observe(activity, mObserver);
}
}
private void runAfterInitialization(Context context, boolean abortOnFailure, Runnable runnable)
/**
* Executes a Runnable after directory initialization has finished.
*
* If this is called when directory initialization already is done,
* the Runnable will be executed immediately. If this is called before
* directory initialization is done, the Runnable will be executed
* after directory initialization finishes successfully, or never
* in case directory initialization doesn't finish successfully.
*
* Calling this function multiple times per object is not supported.
*
* If abortOnFailure is true and external storage was not found, a message
* will be shown to the user and the Runnable will not run. If it is false,
* 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)
{
mDirectoryStateReceiver = new DirectoryStateReceiver(state ->
if (DirectoryInitialization.areDolphinDirectoriesReady())
{
runFinishedCallback();
runnable.run();
}
else if (abortOnFailure &&
showErrorMessage(context,
DirectoryInitialization.getDolphinDirectoriesState().getValue()))
{
runFinishedCallback();
}
else
{
mObserver = createObserver(context, abortOnFailure, runnable);
DirectoryInitialization.getDolphinDirectoriesState().observeForever(mObserver);
}
}
private Observer<DirectoryInitializationState> createObserver(Context context,
boolean abortOnFailure, Runnable runnable)
{
return (state) ->
{
boolean done = state == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
@ -93,22 +138,12 @@ public class AfterDirectoryInitializationRunner
{
runnable.run();
}
});
mLocalBroadcastManager = LocalBroadcastManager.getInstance(context);
IntentFilter statusIntentFilter = new IntentFilter(DirectoryInitialization.BROADCAST_ACTION);
mLocalBroadcastManager.registerReceiver(mDirectoryStateReceiver, statusIntentFilter);
};
}
public void cancel()
{
if (mDirectoryStateReceiver != null)
{
mLocalBroadcastManager.unregisterReceiver(mDirectoryStateReceiver);
mDirectoryStateReceiver = null;
mLocalBroadcastManager = null;
}
DirectoryInitialization.getDolphinDirectoriesState().removeObserver(mObserver);
}
private static boolean showErrorMessage(Context context, DirectoryInitializationState state)

View File

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

View File

@ -6,7 +6,6 @@
package org.dolphinemu.dolphinemu.utils;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Environment;
@ -14,18 +13,17 @@ import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import java.io.File;
import java.io.FileInputStream;
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
@ -33,30 +31,29 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
public final class DirectoryInitialization
{
public static final String BROADCAST_ACTION =
"org.dolphinemu.dolphinemu.DIRECTORY_INITIALIZATION";
public static final String EXTRA_STATE = "directoryState";
private static final int WiimoteNewVersion = 5; // Last changed in PR 8907
private static volatile DirectoryInitializationState directoryState =
DirectoryInitializationState.NOT_YET_INITIALIZED;
private static final MutableLiveData<DirectoryInitializationState> directoryState =
new MutableLiveData<>(DirectoryInitializationState.NOT_YET_INITIALIZED);
private static volatile boolean areDirectoriesAvailable = false;
private static String userPath;
private static AtomicBoolean isDolphinDirectoryInitializationRunning = new AtomicBoolean(false);
private static boolean isUsingLegacyUserDirectory = false;
public enum DirectoryInitializationState
{
NOT_YET_INITIALIZED,
INITIALIZING,
DOLPHIN_DIRECTORIES_INITIALIZED,
CANT_FIND_EXTERNAL_STORAGE
}
public static void start(Context context)
{
if (!isDolphinDirectoryInitializationRunning.compareAndSet(false, true))
if (directoryState.getValue() == DirectoryInitializationState.INITIALIZING)
return;
directoryState.setValue(DirectoryInitializationState.INITIALIZING);
// Can take a few seconds to run, so don't block UI thread.
//noinspection TrivialFunctionalExpressionUsage
((Runnable) () -> init(context)).run();
@ -64,7 +61,7 @@ public final class DirectoryInitialization
private static void init(Context context)
{
if (directoryState != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
if (directoryState.getValue() != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
{
if (setDolphinUserDirectory(context))
{
@ -82,16 +79,13 @@ public final class DirectoryInitialization
EmulationActivity.updateWiimoteNewIniPreferences(context);
}
directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
directoryState.postValue(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
}
else
{
directoryState = DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE;
directoryState.postValue(DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE);
}
}
isDolphinDirectoryInitializationRunning.set(false);
sendBroadcastState(directoryState, context);
}
@Nullable
@ -216,17 +210,18 @@ public final class DirectoryInitialization
public static boolean shouldStart(Context context)
{
return !isDolphinDirectoryInitializationRunning.get() &&
getDolphinDirectoriesState() == DirectoryInitializationState.NOT_YET_INITIALIZED &&
return getDolphinDirectoriesState().getValue() ==
DirectoryInitializationState.NOT_YET_INITIALIZED &&
!isWaitingForWriteAccess(context);
}
public static boolean areDolphinDirectoriesReady()
{
return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
return directoryState.getValue() ==
DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
}
public static DirectoryInitializationState getDolphinDirectoriesState()
public static LiveData<DirectoryInitializationState> getDolphinDirectoriesState()
{
return directoryState;
}
@ -241,14 +236,6 @@ public final class DirectoryInitialization
return userPath;
}
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 boolean copyAsset(String asset, File output, Boolean overwrite, Context context)
{
Log.verbose("[DirectoryInitialization] Copying File " + asset + " to " + output);
@ -400,7 +387,7 @@ public final class DirectoryInitialization
public static boolean isWaitingForWriteAccess(Context context)
{
// This first check is only for performance, not correctness
if (getDolphinDirectoriesState() != DirectoryInitializationState.NOT_YET_INITIALIZED)
if (directoryState.getValue() != DirectoryInitializationState.NOT_YET_INITIALIZED)
return false;
return preferLegacyUserDirectory(context) && !PermissionsHandler.hasWriteAccess(context);

View File

@ -1,27 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization.DirectoryInitializationState;
public class DirectoryStateReceiver extends BroadcastReceiver
{
Action1<DirectoryInitializationState> callback;
public DirectoryStateReceiver(Action1<DirectoryInitializationState> callback)
{
this.callback = callback;
}
@Override
public void onReceive(Context context, Intent intent)
{
DirectoryInitializationState state = (DirectoryInitializationState) intent
.getSerializableExtra(DirectoryInitialization.EXTRA_STATE);
callback.call(state);
}
}

View File

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