Skip to content
Permalink
Browse files
Merge pull request #9318 from JosJuice/android-saf-games
Android: Use storage access framework for game list
  • Loading branch information
JosJuice committed Dec 30, 2020
2 parents 8a3b14d + d78277c commit c1d041b
Show file tree
Hide file tree
Showing 20 changed files with 795 additions and 193 deletions.
@@ -45,6 +45,7 @@
import org.dolphinemu.dolphinemu.overlay.InputOverlay;
import org.dolphinemu.dolphinemu.overlay.InputOverlayPointer;
import org.dolphinemu.dolphinemu.ui.main.MainActivity;
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
import org.dolphinemu.dolphinemu.ui.main.TvMainActivity;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper;
@@ -164,6 +165,11 @@ public final class EmulationActivity extends AppCompatActivity
EmulationActivity.MENU_ACTION_MOTION_CONTROLS);
}

public static void launch(FragmentActivity activity, String filePath)
{
launch(activity, new String[]{filePath});
}

public static void launch(FragmentActivity activity, String[] filePaths)
{
if (sIgnoreLaunchRequests)
@@ -410,11 +416,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent result)
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK)
{
String newDiscPath = FileBrowserHelper.getSelectedPath(result);
if (!TextUtils.isEmpty(newDiscPath))
{
NativeLibrary.ChangeDisc(newDiscPath);
}
NativeLibrary.ChangeDisc(result.getData().toString());
}
}
}
@@ -639,8 +641,10 @@ public void handleMenuAction(@MenuAction int menuAction)
break;

case MENU_ACTION_CHANGE_DISC:
FileBrowserHelper.openFilePicker(this, REQUEST_CHANGE_DISC, false,
FileBrowserHelper.GAME_EXTENSIONS);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_CHANGE_DISC);
break;

case MENU_SET_IR_SENSITIVITY:
@@ -25,6 +25,8 @@
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.TvUtil;

import java.util.Set;

public final class SettingsActivity extends AppCompatActivity implements SettingsActivityView
{
private static final String ARG_MENU_TAG = "menu_tag";
@@ -179,13 +181,19 @@ protected void onActivityResult(int requestCode, int resultCode, Intent result)
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK)
{
if (requestCode == MainPresenter.REQUEST_SD_FILE)
if (requestCode != MainPresenter.REQUEST_DIRECTORY)
{
Uri uri = canonicalizeIfPossible(result.getData());
int takeFlags = result.getFlags() &
(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.RAW_EXTENSION, () ->
Set<String> validExtensions = requestCode == MainPresenter.REQUEST_GAME_FILE ?
FileBrowserHelper.GAME_EXTENSIONS : FileBrowserHelper.RAW_EXTENSION;

int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (requestCode != MainPresenter.REQUEST_GAME_FILE)
flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
int takeFlags = flags & result.getFlags();

FileBrowserHelper.runAfterExtensionCheck(this, uri, validExtensions, () ->
{
getContentResolver().takePersistableUriPermission(uri, takeFlags);
getFragment().getAdapter().onFilePickerConfirmation(uri.toString());
@@ -306,28 +306,17 @@ public void onFilePickerFileClick(SettingsItem item, int position)
mClickedPosition = position;
FilePicker filePicker = (FilePicker) item;

switch (filePicker.getRequestType())
{
case MainPresenter.REQUEST_SD_FILE:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
filePicker.getSelectedValue(mView.getSettings()));
}
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");

mView.getActivity().startActivityForResult(intent, filePicker.getRequestType());
break;
case MainPresenter.REQUEST_GAME_FILE:
FileBrowserHelper.openFilePicker(mView.getActivity(), filePicker.getRequestType(), false,
FileBrowserHelper.GAME_EXTENSIONS);
break;
default:
throw new InvalidParameterException("Unhandled request code");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
filePicker.getSelectedValue(mView.getSettings()));
}

mView.getActivity().startActivityForResult(intent, filePicker.getRequestType());
}

public void onFilePickerConfirmation(String selectedFile)
@@ -16,6 +16,9 @@
*/
public final class SettingsFile
{
public static final String KEY_ISO_PATH_BASE = "ISOPath";
public static final String KEY_ISO_PATHS = "ISOPaths";

public static final String KEY_GCPAD_TYPE = "SIDevice";
public static final String KEY_GCPAD_PLAYER_1 = "SIDevice0";
public static final String KEY_GCPAD_G_TYPE = "PadType";
@@ -1,22 +1,19 @@
package org.dolphinemu.dolphinemu.model;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import androidx.annotation.Keep;

import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
import org.dolphinemu.dolphinemu.utils.ContentHandler;
import org.dolphinemu.dolphinemu.utils.IniFile;

import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.LinkedHashSet;

public class GameFileCache
{
private static final String GAME_FOLDER_PATHS_PREFERENCE = "gameFolderPaths";
private static final Set<String> EMPTY_SET = new HashSet<>();

@Keep
private long mPointer;

@@ -30,70 +27,83 @@ public GameFileCache(String path)
@Override
public native void finalize();

public static void addGameFolder(String path, Context context)
public static void addGameFolder(String path)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
Set<String> folderPaths = preferences.getStringSet(GAME_FOLDER_PATHS_PREFERENCE, EMPTY_SET);
File dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN);
IniFile dolphinIni = new IniFile(dolphinFile);
LinkedHashSet<String> pathSet = getPathSet(false);
int totalISOPaths =
dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0);

if (folderPaths == null)
if (!pathSet.contains(path))
{
return;
dolphinIni.setInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS,
totalISOPaths + 1);
dolphinIni.setString(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE +
totalISOPaths, path);
dolphinIni.save(dolphinFile);
NativeLibrary.ReloadConfig();
}

Set<String> newFolderPaths = new HashSet<>(folderPaths);
newFolderPaths.add(path);
SharedPreferences.Editor editor = preferences.edit();
editor.putStringSet(GAME_FOLDER_PATHS_PREFERENCE, newFolderPaths);
editor.apply();
}

private void removeNonExistentGameFolders(Context context)
private static LinkedHashSet<String> getPathSet(boolean removeNonExistentFolders)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
Set<String> folderPaths = preferences.getStringSet(GAME_FOLDER_PATHS_PREFERENCE, EMPTY_SET);
File dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN);
IniFile dolphinIni = new IniFile(dolphinFile);
LinkedHashSet<String> pathSet = new LinkedHashSet<>();
int totalISOPaths =
dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0);

if (folderPaths == null)
for (int i = 0; i < totalISOPaths; i++)
{
return;
}
String path = dolphinIni.getString(Settings.SECTION_INI_GENERAL,
SettingsFile.KEY_ISO_PATH_BASE + i, "");

Set<String> newFolderPaths = new HashSet<>();
for (String folderPath : folderPaths)
{
File folder = new File(folderPath);
if (folder.exists())
if (path.startsWith("content://") ? ContentHandler.exists(path) : new File(path).exists())
{
newFolderPaths.add(folderPath);
pathSet.add(path);
}
}

if (folderPaths.size() != newFolderPaths.size())
if (removeNonExistentFolders && totalISOPaths > pathSet.size())
{
// One or more folders are being deleted
SharedPreferences.Editor editor = preferences.edit();
editor.putStringSet(GAME_FOLDER_PATHS_PREFERENCE, newFolderPaths);
editor.apply();
int setIndex = 0;

dolphinIni.setInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS,
pathSet.size());

// One or more folders have been removed.
for (String entry : pathSet)
{
dolphinIni.setString(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE +
setIndex, entry);

setIndex++;
}

// Delete known unnecessary keys. Ignore i values beyond totalISOPaths.
for (int i = setIndex; i < totalISOPaths; i++)
{
dolphinIni.deleteKey(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + i);
}

dolphinIni.save(dolphinFile);
NativeLibrary.ReloadConfig();
}

return pathSet;
}

/**
* Scans through the file system and updates the cache to match.
*
* @return true if the cache was modified
*/
public boolean scanLibrary(Context context)
public boolean scanLibrary()
{
boolean recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBooleanGlobal();

removeNonExistentGameFolders(context);

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
Set<String> folderPathsSet = preferences.getStringSet(GAME_FOLDER_PATHS_PREFERENCE, EMPTY_SET);

if (folderPathsSet == null)
{
return false;
}
LinkedHashSet<String> folderPathsSet = getPathSet(true);

String[] folderPaths = folderPathsSet.toArray(new String[0]);

@@ -29,9 +29,10 @@ public final class GameFileCacheService extends IntentService
private static final String ACTION_RESCAN = "org.dolphinemu.dolphinemu.RESCAN_GAME_FILE_CACHE";

private static GameFileCache gameFileCache = null;
private static AtomicReference<GameFile[]> gameFiles = new AtomicReference<>(new GameFile[]{});
private static AtomicBoolean hasLoadedCache = new AtomicBoolean(false);
private static AtomicBoolean hasScannedLibrary = new AtomicBoolean(false);
private static final AtomicReference<GameFile[]> gameFiles =
new AtomicReference<>(new GameFile[]{});
private static final AtomicBoolean hasLoadedCache = new AtomicBoolean(false);
private static final AtomicBoolean hasScannedLibrary = new AtomicBoolean(false);

public GameFileCacheService()
{
@@ -166,7 +167,7 @@ protected void onHandleIntent(Intent intent)
{
synchronized (gameFileCache)
{
boolean changed = gameFileCache.scanLibrary(this);
boolean changed = gameFileCache.scanLibrary();
if (changed)
updateGameFileArray();
hasScannedLibrary.set(true);
@@ -2,6 +2,7 @@

import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
@@ -45,7 +46,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
private FloatingActionButton mFab;
private static boolean sShouldRescanLibrary = true;

private MainPresenter mPresenter = new MainPresenter(this, this);
private final MainPresenter mPresenter = new MainPresenter(this, this);

@Override
protected void onCreate(Bundle savedInstanceState)
@@ -85,7 +86,7 @@ protected void onResume()
.run(this, false, this::setPlatformTabsAndStartGameFileCacheService);
}

mPresenter.addDirIfNeeded(this);
mPresenter.addDirIfNeeded();

// In case the user changed a setting that affects how games are displayed,
// such as system language, cover downloading...
@@ -162,14 +163,17 @@ public void launchSettingsActivity(MenuTag menuTag)
@Override
public void launchFileListActivity()
{
FileBrowserHelper.openDirectoryPicker(this, FileBrowserHelper.GAME_EXTENSIONS);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, MainPresenter.REQUEST_DIRECTORY);
}

@Override
public void launchOpenFileActivity()
{
FileBrowserHelper.openFilePicker(this, MainPresenter.REQUEST_GAME_FILE, false,
FileBrowserHelper.GAME_EXTENSIONS);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, MainPresenter.REQUEST_GAME_FILE);
}

@Override
@@ -194,19 +198,21 @@ protected void onActivityResult(int requestCode, int resultCode, Intent result)
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK)
{
Uri uri = result.getData();
switch (requestCode)
{
case MainPresenter.REQUEST_DIRECTORY:
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedPath(result));
mPresenter.onDirectorySelected(result);
break;

case MainPresenter.REQUEST_GAME_FILE:
EmulationActivity.launch(this, FileBrowserHelper.getSelectedFiles(result));
FileBrowserHelper.runAfterExtensionCheck(this, uri,
FileBrowserHelper.GAME_LIKE_EXTENSIONS,
() -> EmulationActivity.launch(this, result.getData().toString()));
break;

case MainPresenter.REQUEST_WAD_FILE:
FileBrowserHelper.runAfterExtensionCheck(this, result.getData(),
FileBrowserHelper.WAD_EXTENSION,
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.WAD_EXTENSION,
() -> mPresenter.installWAD(result.getData().toString()));
break;
}

0 comments on commit c1d041b

Please sign in to comment.