Merge pull request #428 from lioncash/master

[Android] Implement core autodetect.
This commit is contained in:
Twinaphex 2013-12-18 17:04:12 -08:00
commit 1659adbfe9
9 changed files with 369 additions and 87 deletions

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="3dp">
<ImageView
android:id="@+id/CoreManagerListItemIcon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="6dip"/>
<TextView
android:id="@+id/CoreManagerListItemSubTitle"
android:layout_width="fill_parent"
android:layout_height="26dip"
android:layout_toRightOf="@id/CoreManagerListItemIcon"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:singleLine="true"
android:ellipsize="marquee"/>
<TextView
android:id="@+id/CoreManagerListItemTitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/CoreManagerListItemIcon"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_above="@id/CoreManagerListItemSubTitle"
android:layout_alignWithParentIfMissing="true"
android:gravity="center_vertical"
android:textStyle="bold" />
</RelativeLayout>

View File

@ -5,19 +5,18 @@
<string name="file_type_icon">File type icon</string>
<string name="key_bind_title">Select the button to use</string>
<string name="key_bind_clear">Unbind</string>
<string name="report_refreshrate">Report Refresh Rate</string>
<string name="key_bind_detect">Detect</string>
<string name="optimal_settings_device">Optimal device settings</string>
<string name="extracting_assets_please_wait_">Extracting assets, please wait …</string>
<!-- Main Menu Strings -->
<string name="mainmenu_title">RetroArch - Main Menu</string>
<string name="tv_mode">Resume Content</string>
<string name="resume_content">Resume Content</string>
<string name="load_core">Load Core</string>
<string name="load_game">Load Content</string>
<string name="load_game_history">Load Content (History)</string>
<string name="load_content">Load Content</string>
<string name="load_content_auto">Load Content (Detect Core)</string>
<string name="load_content_history">Load Content (History)</string>
<string name="settings">Settings</string>
<string name="help">Help</string>
<string name="about">About</string>
<string name="quit_retroarch">Quit RetroArch</string>
@ -47,15 +46,16 @@
<string name="refresh_rate_measured_to">Refresh rate measured to: %1$s Hz.</string>
<!-- History Selection Class -->
<string name="recently_played_games">Recently run content</string>
<string name="loading_gamepath">Loading [%1$s]…</string>
<!-- Key Bind Preference Class -->
<string name="current_binding">Current: %1$s</string>
<string name="press_key_to_use">Press key to use</string>
<!-- Core Autodetect strings -->
<string name="multiple_cores_detected">Multiple cores detected</string>
<!-- Main Menu Class -->
<string name="no_core">No core</string>
<string name="welcome_to_retroarch">Welcome to RetroArch</string>
<string name="welcome_to_retroarch_desc">This is your first time starting up RetroArch. RetroArch will now be preconfigured for the best possible user experience.</string>
<string name="gpl_waiver">GPL waiver</string>

View File

@ -4,23 +4,28 @@
<!-- TV Mode -->
<Preference
android:key="retroTVMode"
android:title="@string/tv_mode"/>
android:key="resumeContentPref"
android:title="@string/resume_content"/>
<!-- Load Core -->
<Preference
android:key="loadCorePref"
android:title="@string/load_core"/>
<!-- Load Game -->
<!-- Load Content -->
<Preference
android:key="loadRomPref"
android:title="@string/load_game"/>
android:key="loadContentPref"
android:title="@string/load_content"/>
<!-- Load Game (History) -->
<!-- Load Content (Auto) -->
<Preference
android:key="loadRomHistoryPref"
android:title="@string/load_game_history"/>
android:key="loadContentAutoPref"
android:title="@string/load_content_auto"/>
<!-- Load Content (History) -->
<Preference
android:key="loadContentHistoryPref"
android:title="@string/load_content_history"/>
<!-- Settings -->
<Preference android:title="@string/settings">

View File

@ -52,7 +52,7 @@ public final class CoreSelection extends DialogFragment
// Populate the list
final List<ModuleWrapper> cores = new ArrayList<ModuleWrapper>();
final File[] libs = new File(getActivity().getApplicationInfo().dataDir, "/cores").listFiles();
final File[] libs = new File(getActivity().getApplicationInfo().dataDir, "cores").listFiles();
for (final File lib : libs) {
String libName = lib.getName();

View File

@ -71,7 +71,7 @@ public final class HistorySelection extends DialogFragment
rootView.setOnItemClickListener(onItemClickListener);
// Set the title for this dialog.
getDialog().setTitle(R.string.load_game_history);
getDialog().setTitle(R.string.load_content_history);
// Setup the list adapter
adapter = new IconAdapter<HistoryWrapper>(ctx, R.layout.line_list_item);

View File

@ -115,6 +115,17 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWr
}
}
/**
* Same as the original constructor, but allows for string paths.
*
* @param context The current {@link Context}.
* @param path Path to the file to encapsulate.
*/
public ModuleWrapper(Context context, String path)
{
this(context, new File(path));
}
/**
* Gets the underlying {@link File} instance for this ModuleWrapper.
*

View File

@ -0,0 +1,295 @@
package com.retroarch.browser.dirfragment;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.retroarch.R;
import com.retroarch.browser.FileWrapper;
import com.retroarch.browser.IconAdapter;
import com.retroarch.browser.ModuleWrapper;
import com.retroarch.browser.preferences.util.UserPreferences;
import com.retroarch.browser.retroactivity.RetroActivityFuture;
import com.retroarch.browser.retroactivity.RetroActivityPast;
/**
* {@link DirectoryFragment} that implements core autodetect.
* <p>
* Basically, how it works is the user selects a file.
* Then, we iterate over all the cores and check what their supported extensions are.
* Then, if any cores contain the supported extension, they are added to a list and
* displayed to the user to choose from.
* <p>
* The only exception is if only one core matches the extension of the chosen file.
* In this case, we just attempt to launch the core with that file directly.
*/
// TODO: This is ugly as hell. Clean this up sometime.
// For example, maybe breaking this out into two fragments
// to handle the behavior would be better. One for browsing,
// one for handling the list of selectable cores.
public final class DetectCoreDirectoryFragment extends DirectoryFragment
{
private ListView backingListView = null;
private boolean inFileBrowser = true;
private ArrayList<String> supportedCorePaths = new ArrayList<String>();
/**
* Retrieves a new instance of a DetectCoreDirectoryFragment
* with a title specified by the given resource ID.
*
* @param titleResId String resource ID for the title
* of this DetectCoreDirectoryFragment.
*
* @return A new instance of a DetectCoreDirectoryFragment.
*/
public static DetectCoreDirectoryFragment newInstance(int titleResId)
{
final DetectCoreDirectoryFragment dFrag = new DetectCoreDirectoryFragment();
final Bundle bundle = new Bundle();
bundle.putInt("titleResId", titleResId);
dFrag.setArguments(bundle);
return dFrag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
backingListView = (ListView) inflater.inflate(R.layout.line_list, container, false);
backingListView.setOnItemClickListener(onItemClickListener);
// Get whether or not we were in the file browser prior to recreation.
if (savedInstanceState != null)
{
inFileBrowser = savedInstanceState.getBoolean("inFileBrowser");
if (inFileBrowser)
backStack = savedInstanceState.getParcelableArrayList("BACKSTACK");
}
// Set the dialog title.
if (inFileBrowser)
getDialog().setTitle(getArguments().getInt("titleResId"));
else
getDialog().setTitle(R.string.multiple_cores_detected);
// If we're in the file browser, reinitialize the file list adapter.
if (savedInstanceState == null || inFileBrowser)
{
// Setup the list
adapter = new IconAdapter<FileWrapper>(getActivity(), R.layout.line_list_item);
backingListView.setAdapter(adapter);
}
if (inFileBrowser)
{
if (backStack == null || backStack.isEmpty())
{
backStack = new ArrayList<BackStackItem>();
String startPath = (startDirectory == null || startDirectory.isEmpty()) ? Environment
.getExternalStorageDirectory().getPath() : startDirectory;
backStack.add(new BackStackItem(startPath, false));
}
wrapFiles();
}
else // Rebuild the core adapter.
{
supportedCorePaths = savedInstanceState.getStringArrayList("coreFilePaths");
CoreSelectionAdapter adapter = new CoreSelectionAdapter(getActivity(), android.R.layout.simple_list_item_2);
for (String path : supportedCorePaths)
{
ModuleWrapper mw = new ModuleWrapper(getActivity(), path);
adapter.add(new CoreItem(mw.getInternalName(), mw.getEmulatedSystemName()));
}
backingListView.setAdapter(adapter);
}
return backingListView;
}
@Override
public void onSaveInstanceState(Bundle outState)
{
// Save whether or not we're in core selection or the file browser.
outState.putBoolean("inFileBrowser", inFileBrowser);
if (!inFileBrowser)
outState.putStringArrayList("coreFilePaths", supportedCorePaths);
}
private File chosenFile = null;
private final OnItemClickListener onItemClickListener = new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
final FileWrapper item = adapter.getItem(position);
if (inFileBrowser && item.isParentItem() && backStack.get(backStack.size() - 1).parentIsBack)
{
backStack.remove(backStack.size() - 1);
wrapFiles();
return;
}
final File selected = item.isParentItem() ? listedDirectory.getParentFile() : item.getFile();
if (inFileBrowser && selected.isDirectory())
{
Log.d("DirectoryFrag", "Is Directory.");
backStack.add(new BackStackItem(selected.getAbsolutePath(), !item.isParentItem()));
wrapFiles();
return;
}
else if (inFileBrowser && selected.isFile())
{
String filePath = selected.getAbsolutePath();
String fileExt = "";
chosenFile = selected;
// Attempt to get the file extension.
int i = filePath.lastIndexOf('.');
if (i >= 0)
fileExt = filePath.substring(i+1);
// Enumerate the cores and check for the extension
File coreDir = new File(getActivity().getApplicationInfo().dataDir + File.separator + "cores");
File[] coreFiles = coreDir.listFiles();
List<ModuleWrapper>supportedCores = new ArrayList<ModuleWrapper>();
for (File core : coreFiles)
{
ModuleWrapper mw = new ModuleWrapper(getActivity(), core);
if (mw.getSupportedExtensions().contains(fileExt))
{
supportedCores.add(mw);
supportedCorePaths.add(mw.getUnderlyingFile().getAbsolutePath());
}
}
// If only one core is supported,
if (supportedCores.size() == 1)
{
launchCore(selected.getPath(), supportedCores.get(0).getUnderlyingFile().getPath());
}
// Otherwise build the list for the user to choose from.
else if (supportedCores.size() > 1)
{
// Modify the title to notify of multiple cores.
getDialog().setTitle(R.string.multiple_cores_detected);
// Add all the cores to the adapter and swap it with the one in the ListView.
final CoreSelectionAdapter csa = new CoreSelectionAdapter(getActivity(), android.R.layout.simple_list_item_2);
for (ModuleWrapper core : supportedCores)
{
csa.add(new CoreItem(core.getInternalName(), core.getEmulatedSystemName()));
}
backingListView.setAdapter(csa);
}
}
else // Selection made
{
launchCore(chosenFile.getPath(), DetectCoreDirectoryFragment.this.supportedCorePaths.get(position));
}
// Not in the file browser any more.
inFileBrowser = false;
}
};
private void launchCore(String contentPath, String corePath)
{
Intent retro;
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB))
retro = new Intent(getActivity(), RetroActivityFuture.class);
else
retro = new Intent(getActivity(), RetroActivityPast.class);
UserPreferences.updateConfigFile(getActivity());
String current_ime = Settings.Secure.getString(getActivity().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
retro.putExtra("ROM", contentPath);
retro.putExtra("LIBRETRO", corePath);
retro.putExtra("CONFIGFILE", UserPreferences.getDefaultConfigPath(getActivity()));
retro.putExtra("IME", current_ime);
startActivity(retro);
dismiss();
}
// Used to represent data in the ListView after the user chooses an item.
private static final class CoreItem
{
public final String Title;
public final String Subtitle;
public CoreItem(String title, String subtitle)
{
this.Title = title;
this.Subtitle = subtitle;
}
}
// Adapter that the ListView is flipped to after choosing a file to launch.
private static final class CoreSelectionAdapter extends ArrayAdapter<CoreItem>
{
private final int resourceId;
/**
* Constructor
*
* @param context The current {@link Context}.
* @param resourceId The resource ID for a layout file.
*/
public CoreSelectionAdapter(Context context, int resourceId)
{
super(context, resourceId);
this.resourceId = resourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(resourceId, parent, false);
}
final CoreItem core = getItem(position);
if (core != null)
{
final TextView title = (TextView) convertView.findViewById(android.R.id.text1);
final TextView subtitle = (TextView) convertView.findViewById(android.R.id.text2);
if (title != null)
title.setText(core.Title);
if (subtitle != null)
subtitle.setText(core.Subtitle);
}
return convertView;
}
}
}

View File

@ -6,7 +6,6 @@ import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.ListFragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -25,7 +24,7 @@ import java.io.*;
/**
* {@link ListFragment} subclass that provides a file-browser
* {@link DialogFragment} subclass that provides a file-browser
* like UI for browsing for specific files.
* <p>
* This file browser also allows for custom filtering
@ -38,15 +37,15 @@ import java.io.*;
* To instantiate a new instance of this class
* you must use the {@code newInstance} method.
*/
public final class DirectoryFragment extends DialogFragment
public class DirectoryFragment extends DialogFragment
{
private IconAdapter<FileWrapper> adapter;
private File listedDirectory;
protected IconAdapter<FileWrapper> adapter;
protected File listedDirectory;
public static final class BackStackItem implements Parcelable
{
private final String path;
private final boolean parentIsBack;
protected final String path;
protected boolean parentIsBack;
public BackStackItem(String path, boolean parentIsBack)
{
@ -101,12 +100,11 @@ public final class DirectoryFragment extends DialogFragment
}
private ArrayList<BackStackItem> backStack;
private String startDirectory;
private String pathSettingKey;
private boolean isDirectoryTarget;
private OnDirectoryFragmentClosedListener onClosedListener;
protected ArrayList<BackStackItem> backStack;
protected String startDirectory;
protected String pathSettingKey;
protected boolean isDirectoryTarget;
protected OnDirectoryFragmentClosedListener onClosedListener;
/**
* Sets the starting directory for this DirectoryFragment
@ -349,7 +347,7 @@ public final class DirectoryFragment extends DialogFragment
disallowedExt.addAll(Arrays.asList(exts));
}
private void wrapFiles()
protected void wrapFiles()
{
listedDirectory = new File(backStack.get(backStack.size() - 1).path);

View File

@ -28,6 +28,7 @@ import com.retroarch.R;
import com.retroarch.browser.CoreSelection;
import com.retroarch.browser.HistorySelection;
import com.retroarch.browser.NativeInterface;
import com.retroarch.browser.dirfragment.DetectCoreDirectoryFragment;
import com.retroarch.browser.dirfragment.DirectoryFragment;
import com.retroarch.browser.dirfragment.DirectoryFragment.OnDirectoryFragmentClosedListener;
import com.retroarch.browser.mainmenu.gplwaiver.GPLWaiverDialogFragment;
@ -65,10 +66,11 @@ public final class MainMenuFragment extends PreferenceListFragment implements On
addPreferencesFromResource(R.xml.main_menu);
// Set the listeners for the menu items
findPreference("retroTVMode").setOnPreferenceClickListener(this);
findPreference("resumeContentPref").setOnPreferenceClickListener(this);
findPreference("loadCorePref").setOnPreferenceClickListener(this);
findPreference("loadRomPref").setOnPreferenceClickListener(this);
findPreference("loadRomHistoryPref").setOnPreferenceClickListener(this);
findPreference("loadContentAutoPref").setOnPreferenceClickListener(this);
findPreference("loadContentPref").setOnPreferenceClickListener(this);
findPreference("loadContentHistoryPref").setOnPreferenceClickListener(this);
findPreference("quitRetroArch").setOnPreferenceClickListener(this);
// Extract assets.
@ -340,8 +342,8 @@ public final class MainMenuFragment extends PreferenceListFragment implements On
{
final String prefKey = preference.getKey();
// TV Mode
if (prefKey.equals("retroTVMode"))
// Resume Content
if (prefKey.equals("resumeContentPref"))
{
UserPreferences.updateConfigFile(ctx);
@ -360,30 +362,43 @@ public final class MainMenuFragment extends PreferenceListFragment implements On
CoreSelection.newInstance().show(getFragmentManager(), "core_selection");
}
// Load ROM Preference
else if (prefKey.equals("loadRomPref"))
else if (prefKey.equals("loadContentPref"))
{
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
final String libretro_path = prefs.getString("libretro_path", ctx.getApplicationInfo().dataDir + "/cores");
if (!new File(libretro_path).isDirectory())
{
final DirectoryFragment romBrowser = DirectoryFragment.newInstance(R.string.load_game);
romBrowser.addDisallowedExts(".state", ".srm", ".state.auto", ".rtc");
romBrowser.setOnDirectoryFragmentClosedListener(this);
final DirectoryFragment contentBrowser = DirectoryFragment.newInstance(R.string.load_content);
contentBrowser.addDisallowedExts(".state", ".srm", ".state.auto", ".rtc");
contentBrowser.setOnDirectoryFragmentClosedListener(this);
final String startPath = prefs.getString("rgui_browser_directory", "");
if (!startPath.isEmpty() && new File(startPath).exists())
romBrowser.setStartDirectory(startPath);
contentBrowser.setStartDirectory(startPath);
romBrowser.show(getFragmentManager(), "romBrowser");
contentBrowser.show(getFragmentManager(), "contentBrowser");
}
else
{
Toast.makeText(ctx, R.string.load_a_core_first, Toast.LENGTH_SHORT).show();
}
}
// Load ROM (History) Preference
else if (prefKey.equals("loadRomHistoryPref"))
else if (prefKey.equals("loadContentAutoPref"))
{
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
final DetectCoreDirectoryFragment contentBrowser = DetectCoreDirectoryFragment.newInstance(R.string.load_content_auto);
contentBrowser.addDisallowedExts(".state", ".srm", ".state.auto", ".rtc");
contentBrowser.setOnDirectoryFragmentClosedListener(this);
final String startPath = prefs.getString("rgui_browser_directory", "");
if (!startPath.isEmpty() && new File(startPath).exists())
contentBrowser.setStartDirectory(startPath);
contentBrowser.show(getFragmentManager(), "contentBrowser");
}
// Load Content (History) Preference
else if (prefKey.equals("loadContentHistoryPref"))
{
HistorySelection.newInstance().show(getFragmentManager(), "history_selection");
}