[Android] Add an info view to the core manager InstalledCoresFragment. Also added a dual-fragment layout of this for tablet devices as well.

This commit is contained in:
Lioncash 2013-11-06 23:39:15 -05:00
parent cb1381c94f
commit 48fd723015
12 changed files with 447 additions and 66 deletions

View File

@ -25,7 +25,6 @@
<category android:name="tv.ouya.intent.category.GAME" />
</intent-filter>
</activity>
<activity android:name="com.retroarch.browser.HelpActivity"/>
<activity android:name="com.retroarch.browser.FileWrapper"/>
<activity android:name="com.retroarch.browser.RetroTVMode"/>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:baselineAligned="false"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<FrameLayout
android:layout_weight="1"
android:id="@+id/installed_cores_fragment_container1"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<FrameLayout
android:layout_weight="1"
android:id="@+id/installed_cores_fragment_container2"
android:layout_width="0dp"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_weight="1"
android:id="@+id/installed_cores_fragment_container1"
android:layout_width="0dp"
android:layout_height="match_parent" />

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="?android:attr/activatedBackgroundIndicator"
android:choiceMode="singleChoice"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -17,7 +17,6 @@
<string name="load_core">Load Core</string>
<string name="load_game">Load Game</string>
<string name="load_game_history">Load Game (History)</string>
<string name="manage_cores">Manage Cores</string>
<string name="settings">Settings</string>
<string name="help">Help</string>
<string name="about">About</string>
@ -33,6 +32,14 @@
<string name="uninstall_success">Successfully uninstalled core: %1$s.</string>
<string name="downloadable_cores">Downloadable Cores</string>
<!-- Core Manager Info Fragment -->
<string name="core_info_displayNameTitle">Display Name</string>
<string name="core_info_internalNameTitle">Internal Name</string>
<string name="core_info_systemNameTitle">Emulated Systems</string>
<string name="core_info_emu_author">Emulator Authors</string>
<string name="core_info_licenseTitle">Core License</string>
<string name="core_info_manufacterer">Manufacturer</string>
<!-- Display Refresh Rate Test Class -->
<string name="refresh_rate_calibration">Refresh rate calibration</string>
<string name="touch_screen_with_fingers">Touch the screen with your fingers for more accurate measurements.</string>
@ -51,7 +58,7 @@
<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 gameplay experience.</string>
<string name="gpl_waiver">GPL waiver</string>
<string name="gpl_waiver_desc">Copyright (C) 2010-2013 RetroArch Team.\n\nThis program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along with this program; if not, see www.gnu.org/licenses.\n\nAdditional permission under GNU GPL version 3 section 7\n\nIf you modify this Program, or any covered work, by linking or combining it with any non-GPL libretro core, containing parts covered by the terms of the core\'s license, the licensors of this Program grant you additional permission to convey the resulting work.</string>
<string name="gpl_waiver_desc">Copyright © 20102013 RetroArch Team.\n\nThis program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along with this program; if not, see www.gnu.org/licenses.\n\nAdditional permission under GNU GPL version 3 section 7\n\nIf you modify this Program, or any covered work, by linking or combining it with any non-GPL libretro core, containing parts covered by the terms of the core\'s license, the licensors of this Program grant you additional permission to convey the resulting work.</string>
<string name="detect_device_msg_ouya">The ideal configuration options for your device will now be preconfigured.\n\nNOTE: For optimal performance, turn off Google Account sync, GPS and Wi-Fi in your Android settings menu.</string>
<string name="detect_device_msg_general">The ideal configuration options for your device will now be preconfigured.\n\nNOTE: For optimal performance, turn off Google Account sync, Google Play Store auto-updates, GPS and Wi-Fi in your Android settings menu.</string>
<string name="nvidia_shield_detected">NVidia Shield detected</string>

View File

@ -22,6 +22,7 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWr
private final String manufacturer;
private final String systemName;
private final String license;
private final List<String> authors;
private final List<String> supportedExtensions;
/**
@ -54,29 +55,40 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWr
final ConfigFile infoFile = new ConfigFile(infoFilePath);
// Now read info out of the info file. Make them an empty string if the key doesn't exist.
this.displayName = (infoFile.keyExists("display_name")) ? infoFile.getString("display_name") : "";
this.coreName = (infoFile.keyExists("corename")) ? infoFile.getString("corename") : "";
this.systemName = (infoFile.keyExists("systemname")) ? infoFile.getString("systemname") : "";
this.manufacturer = (infoFile.keyExists("manufacturer")) ? infoFile.getString("manufacturer") : "";
this.license = (infoFile.keyExists("license")) ? infoFile.getString("license") : "";
this.displayName = (infoFile.keyExists("display_name")) ? infoFile.getString("display_name") : "N/A";
this.coreName = (infoFile.keyExists("corename")) ? infoFile.getString("corename") : "N/A";
this.systemName = (infoFile.keyExists("systemname")) ? infoFile.getString("systemname") : "N/A";
this.manufacturer = (infoFile.keyExists("manufacturer")) ? infoFile.getString("manufacturer") : "N/A";
this.license = (infoFile.keyExists("license")) ? infoFile.getString("license") : "N/A";
// Getting supported extensions is a little different.
// Getting supported extensions and authors is a little different.
// We need to split at every '|' character, since it is
// the delimiter for a new extension that the core supports.
//
// Cores that don't have multiple extensions supported
// don't contain the '|' delimiter, so we just create a String array with
// a size of 1, and just directly assign the retrieved extensions to it.
// don't contain the '|' delimiter, so we just create a String list
// and just directly assign the retrieved extensions to it.
final String supportedExts = infoFile.getString("supported_extensions");
if (supportedExts != null && supportedExts.contains("|"))
{
this.supportedExtensions = new ArrayList<String>(Arrays.asList(supportedExts.split("|")));
this.supportedExtensions = new ArrayList<String>(Arrays.asList(supportedExts.split("\\|")));
}
else
{
this.supportedExtensions = new ArrayList<String>();
this.supportedExtensions = new ArrayList<String>();
this.supportedExtensions.add(supportedExts);
}
final String emuAuthors = infoFile.getString("authors");
if (emuAuthors != null && emuAuthors.contains("|"))
{
this.authors = new ArrayList<String>(Arrays.asList(emuAuthors.split("\\|")));
}
else
{
this.authors = new ArrayList<String>();
this.authors.add(emuAuthors);
}
}
else // No info file.
{
@ -84,6 +96,7 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWr
this.systemName = "N/A";
this.manufacturer = "N/A";
this.license = "N/A";
this.authors = new ArrayList<String>();
this.supportedExtensions = new ArrayList<String>();
this.coreName = coreName;
}
@ -109,6 +122,26 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWr
return displayName;
}
/**
* Gets the internal core name for this wrapped core.
*
* @return the internal core name for this wrapped core.
*/
public String getInternalName()
{
return coreName;
}
/**
* Gets the name of the system that is emulated by this wrapped core.
*
* @return the name of the system that is emulated by this wrapped core.
*/
public String getEmulatedSystemName()
{
return systemName;
}
/**
* Gets the license that this core is protected under.
*
@ -131,6 +164,16 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable<ModuleWr
return manufacturer;
}
/**
* Gets the list of authors of this emulator core.
*
* @return the list of authors of this emulator core.
*/
public List<String> getEmulatorAuthors()
{
return authors;
}
/**
* Gets the List of supported extensions for this core.
*

View File

@ -1,8 +1,10 @@
package com.retroarch.browser.coremanager;
import java.util.List;
import com.retroarch.R;
import com.retroarch.browser.coremanager.fragments.DownloadableCoresFragment;
import com.retroarch.browser.coremanager.fragments.InstalledCoresFragment;
import com.retroarch.browser.coremanager.fragments.InstalledCoresManagerFragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -75,6 +77,52 @@ public final class CoreManagerActivity extends ActionBarActivity implements TabL
// Do nothing. Not used.
}
@Override
public void onBackPressed()
{
if (!returnBackStackImmediate(getSupportFragmentManager()))
{
super.onBackPressed();
}
}
// HACK: Propagate back button press to child fragments.
// This might not work properly when you have multiple fragments
// adding multiple children to the backstack. (in our case, only
// one child fragments adds fragments to the backstack, so we're fine with this).
//
// Congrats to Google for having a bugged backstack that doesn't account for
// nested fragments. A heavy applause to them for the immense stupidity if this is
// actually intended behavior. This is why overriding the handling of back presses
// should be present in Fragments.
//
// Taken from: http://android.joao.jp/2013/09/back-stack-with-nested-fragments-back.html
// If you ever read this, thank you very much for making the workaround.
//
private boolean returnBackStackImmediate(FragmentManager fm)
{
List<Fragment> fragments = fm.getFragments();
if (fragments != null && fragments.size() > 0)
{
for (Fragment fragment : fragments)
{
if (fragment.getChildFragmentManager().getBackStackEntryCount() > 0)
{
if (fragment.getChildFragmentManager().popBackStackImmediate())
{
return true;
}
else
{
return returnBackStackImmediate(fragment.getChildFragmentManager());
}
}
}
}
return false;
}
// Adapter for the core manager ViewPager.
private final class ViewPagerAdapter extends FragmentPagerAdapter
{
@ -94,7 +142,7 @@ public final class CoreManagerActivity extends ActionBarActivity implements TabL
switch (position)
{
case 0:
return new InstalledCoresFragment();
return new InstalledCoresManagerFragment();
case 1:
return new DownloadableCoresFragment();

View File

@ -4,9 +4,9 @@ import android.support.v4.app.ListFragment;
/**
* {@link ListFragment} that is responsible for showing
* cores that are able to be downloaded or are not installed..
* cores that are able to be downloaded or are not installed.
*/
public final class DownloadableCoresFragment extends ListFragment
{
// TODO: Implement.
}

View File

@ -0,0 +1,120 @@
package com.retroarch.browser.coremanager.fragments;
import java.io.File;
import com.retroarch.R;
import com.retroarch.browser.ModuleWrapper;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
/**
* Fragment that displays information about a selected core.
*/
public final class InstalledCoreInfoFragment extends ListFragment
{
/**
* Creates a new instance of a InstalledCoreInfoFragment.
*
* @param core The wrapped core to represent.
*
* @return a new instance of a InstalledCoreInfoFragment.
*/
public static InstalledCoreInfoFragment newInstance(ModuleWrapper core)
{
InstalledCoreInfoFragment cif = new InstalledCoreInfoFragment();
// Set the core path as an argument.
// This will allow us to re-retrieve information if the Fragment
// is destroyed upon state changes
Bundle args = new Bundle();
args.putString("core_path", core.getUnderlyingFile().getPath());
cif.setArguments(args);
return cif;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Inflate the view.
ListView infoView = (ListView) inflater.inflate(R.layout.coremanager_listview, container, false);
// Get the appropriate info providers.
final Bundle args = getArguments();
final ModuleWrapper core = new ModuleWrapper(getActivity(), new File(args.getString("core_path")));
// Initialize the core info.
CoreInfoAdapter adapter = new CoreInfoAdapter(getActivity(), android.R.layout.simple_list_item_2);
adapter.add(new InstalledCoreInfoItem(getString(R.string.core_info_displayNameTitle), core.getDisplayName()));
adapter.add(new InstalledCoreInfoItem(getString(R.string.core_info_internalNameTitle), core.getInternalName()));
adapter.add(new InstalledCoreInfoItem(getString(R.string.core_info_systemNameTitle), core.getEmulatedSystemName()));
adapter.add(new InstalledCoreInfoItem(getString(R.string.core_info_manufacterer), core.getManufacturer()));
adapter.add(new InstalledCoreInfoItem(getString(R.string.core_info_emu_author), core.getEmulatorAuthors()));
adapter.add(new InstalledCoreInfoItem(getString(R.string.core_info_licenseTitle), core.getCoreLicense()));
// Set the list adapter.
infoView.setAdapter(adapter);
return infoView;
}
/**
* Adapter backing this InstalledCoreInfoFragment
*/
private final class CoreInfoAdapter extends ArrayAdapter<InstalledCoreInfoItem>
{
private final Context context;
private final int resourceId;
/**
* Constructor
*
* @param context The current {@link Context}.
* @param resourceId The resource ID for a layout file containing a layout to use when instantiating views.
*/
public CoreInfoAdapter(Context context, int resourceId)
{
super(context, resourceId);
this.context = context;
this.resourceId = resourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
{
LayoutInflater vi = LayoutInflater.from(context);
convertView = vi.inflate(resourceId, parent, false);
}
final InstalledCoreInfoItem item = getItem(position);
if (item != 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(item.getTitle());
}
if (subtitle != null)
{
subtitle.setText(item.getSubtitle());
}
}
return convertView;
}
}
}

View File

@ -0,0 +1,60 @@
package com.retroarch.browser.coremanager.fragments;
import java.util.List;
import android.text.TextUtils;
/**
* Represents a single list item within the InstalledCoreInfoFragment.
*/
public final class InstalledCoreInfoItem
{
private final String title;
private final String subtitle;
/**
* Constructor
*
* @param title Title of the item within the core info list.
* @param subtitle Subtitle of the item within the core info list.
*/
public InstalledCoreInfoItem(String title, String subtitle)
{
this.title = title;
this.subtitle = subtitle;
}
/**
* Constructor.
* <p>
* Allows for creating a subtitle out of multiple strings.
*
* @param title Title of the item within the core info list.
* @param subtitle List of strings to add to the subtitle section of this item.
*/
public InstalledCoreInfoItem(String title, List<String> subtitle)
{
this.title = title;
this.subtitle = TextUtils.join(", ", subtitle);
}
/**
* Gets the title of this InstalledCoreInfoItem.
*
* @return the title of this InstalledCoreInfoItem.
*/
public String getTitle()
{
return title;
}
/**
* Gets the subtitle of this InstalledCoreInfoItem.
*
* @return the subtitle of this InstalledCoreInfoItem.
*/
public String getSubtitle()
{
return subtitle;
}
}

View File

@ -21,23 +21,51 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
/**
* {@link ListFragment} that displays all of the currently installed cores
* <p>
* In terms of layout, this is the fragment that is placed on the
* left side of the screen within the core manager
*/
public final class InstalledCoresFragment extends ListFragment
{
// Callback for the interface.
private OnCoreItemClickedListener callback;
// Adapter backing this ListFragment.
private InstalledCoresAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState)
/**
* Interface that a parent fragment must implement
* in order to display the core info view.
*/
interface OnCoreItemClickedListener
{
super.onCreate(savedInstanceState);
/**
* The action to perform when a core is selected within the list view.
*
* @param position The position of the item in the list.
* @param core A reference to the actual {@link ModuleWrapper}
* represented by that list item.
*/
void onCoreItemClicked(int position, ModuleWrapper core);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Inflate the layout for this ListFragment.
ListView parentView = (ListView) inflater.inflate(R.layout.coremanager_listview, container, false);
// Set the long click listener.
parentView.setOnItemLongClickListener(itemLongClickListener);
// Get the callback. (implemented within InstalledCoresManagerFragment).
callback = (OnCoreItemClickedListener) getParentFragment();
// The list of items that will be added to the adapter backing this ListFragment.
final List<ModuleWrapper> items = new ArrayList<ModuleWrapper>();
@ -48,58 +76,59 @@ public final class InstalledCoresFragment extends ListFragment
// Populate the list
final File[] libs = new File(getActivity().getApplicationInfo().dataDir, "/cores").listFiles();
for (File lib : libs)
if (libs != null)
{
String libName = lib.getName();
// Never append a NEON lib if we don't have NEON.
if (libName.contains("neon") && !supportsNeon)
continue;
// If we have a NEON version with NEON capable CPU,
// never append a non-NEON version.
if (supportsNeon && !libName.contains("neon"))
for (File lib : libs)
{
boolean hasNeonVersion = false;
for (File lib_ : libs)
{
String otherName = lib_.getName();
String baseName = libName.replace(".so", "");
if (otherName.contains("neon") && otherName.startsWith(baseName))
{
hasNeonVersion = true;
break;
}
}
if (hasNeonVersion)
String libName = lib.getName();
// Never append a NEON lib if we don't have NEON.
if (libName.contains("neon") && !supportsNeon)
continue;
// If we have a NEON version with NEON capable CPU,
// never append a non-NEON version.
if (supportsNeon && !libName.contains("neon"))
{
boolean hasNeonVersion = false;
for (File lib_ : libs)
{
String otherName = lib_.getName();
String baseName = libName.replace(".so", "");
if (otherName.contains("neon") && otherName.startsWith(baseName))
{
hasNeonVersion = true;
break;
}
}
if (hasNeonVersion)
continue;
}
// Add it to the list.
items.add(new ModuleWrapper(getActivity(), lib));
}
// Add it to the list.
items.add(new ModuleWrapper(getActivity(), lib));
}
// Sort the list alphabetically
Collections.sort(items);
// Initialize and set the backing adapter for this ListFragment.
adapter = new InstalledCoresAdapter(getActivity(), R.layout.coremanager_list_item, items);
setListAdapter(adapter);
adapter = new InstalledCoresAdapter(getActivity(), android.R.layout.simple_list_item_2, items);
parentView.setAdapter(adapter);
return parentView;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
public void onListItemClick(ListView l, View v, int position, long id)
{
// Inflate the layout for this ListFragment.
View parentView = inflater.inflate(R.layout.coremanager_listview, container, false);
// Set the long click listener.
ListView mainList = (ListView) parentView.findViewById(android.R.id.list);
mainList.setOnItemLongClickListener(itemLongClickListener);
callback.onCoreItemClicked(position, adapter.getItem(position));
return mainList;
// Set the item as checked so it highlights in the two-fragment view.
getListView().setItemChecked(position, true);
}
// This will be the handler for long clicks on individual list items in this ListFragment.
@ -181,9 +210,8 @@ public final class InstalledCoresFragment extends ListFragment
final ModuleWrapper item = items.get(position);
if (item != null)
{
TextView title = (TextView) convertView.findViewById(R.id.CoreManagerListItemTitle);
TextView subtitle = (TextView) convertView.findViewById(R.id.CoreManagerListItemSubTitle);
ImageView icon = (ImageView) convertView.findViewById(R.id.CoreManagerListItemIcon);
TextView title = (TextView) convertView.findViewById(android.R.id.text1);
TextView subtitle = (TextView) convertView.findViewById(android.R.id.text2);
if (title != null)
{
@ -194,11 +222,6 @@ public final class InstalledCoresFragment extends ListFragment
{
subtitle.setText(item.getSubText());
}
if (icon != null)
{
// TODO: Set core icon.
}
}
return convertView;

View File

@ -0,0 +1,53 @@
package com.retroarch.browser.coremanager.fragments;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.retroarch.R;
import com.retroarch.browser.ModuleWrapper;
/**
* Underlying {@link Fragment} that manages layout functionality
* for the two fragments that rest inside of this one.
*/
public class InstalledCoresManagerFragment extends Fragment implements InstalledCoresFragment.OnCoreItemClickedListener
{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.coremanager_installed_cores_base, container, false);
final Fragment installedCores = new InstalledCoresFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.installed_cores_fragment_container1, installedCores);
ft.commit();
return v;
}
@Override
public void onCoreItemClicked(int position, ModuleWrapper core)
{
// If this view does not exist, it means the screen
// is not considered 'large' and thus, we use the single fragment layout.
if (getView().findViewById(R.id.installed_cores_fragment_container2) == null)
{
InstalledCoreInfoFragment cif = InstalledCoreInfoFragment.newInstance(core);
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.installed_cores_fragment_container1, cif);
ft.addToBackStack(null);
ft.commit();
}
else // Large screen
{
InstalledCoreInfoFragment cif = InstalledCoreInfoFragment.newInstance(core);
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.installed_cores_fragment_container2, cif);
ft.commit();
}
}
}