From 48fd723015db7277f3d484d76927fc7b5ec86906 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 6 Nov 2013 23:39:15 -0500 Subject: [PATCH] [Android] Add an info view to the core manager InstalledCoresFragment. Also added a dual-fragment layout of this for tablet devices as well. --- android/phoenix/AndroidManifest.xml | 1 - .../coremanager_installed_cores_base.xml | 19 +++ .../coremanager_installed_cores_base.xml | 7 + .../res/layout/coremanager_listview.xml | 2 + android/phoenix/res/values/strings.xml | 11 +- .../com/retroarch/browser/ModuleWrapper.java | 63 +++++++-- .../coremanager/CoreManagerActivity.java | 52 +++++++- .../fragments/DownloadableCoresFragment.java | 4 +- .../fragments/InstalledCoreInfoFragment.java | 120 +++++++++++++++++ .../fragments/InstalledCoreInfoItem.java | 60 +++++++++ .../fragments/InstalledCoresFragment.java | 121 +++++++++++------- .../InstalledCoresManagerFragment.java | 53 ++++++++ 12 files changed, 447 insertions(+), 66 deletions(-) create mode 100644 android/phoenix/res/layout-large/coremanager_installed_cores_base.xml create mode 100644 android/phoenix/res/layout/coremanager_installed_cores_base.xml create mode 100644 android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoFragment.java create mode 100644 android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoItem.java create mode 100644 android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java diff --git a/android/phoenix/AndroidManifest.xml b/android/phoenix/AndroidManifest.xml index b9d2f1061e..43bce6dae3 100644 --- a/android/phoenix/AndroidManifest.xml +++ b/android/phoenix/AndroidManifest.xml @@ -25,7 +25,6 @@ - diff --git a/android/phoenix/res/layout-large/coremanager_installed_cores_base.xml b/android/phoenix/res/layout-large/coremanager_installed_cores_base.xml new file mode 100644 index 0000000000..2fd9803ee3 --- /dev/null +++ b/android/phoenix/res/layout-large/coremanager_installed_cores_base.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/android/phoenix/res/layout/coremanager_installed_cores_base.xml b/android/phoenix/res/layout/coremanager_installed_cores_base.xml new file mode 100644 index 0000000000..43f47f627b --- /dev/null +++ b/android/phoenix/res/layout/coremanager_installed_cores_base.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/phoenix/res/layout/coremanager_listview.xml b/android/phoenix/res/layout/coremanager_listview.xml index 7e1f57d214..9058846980 100644 --- a/android/phoenix/res/layout/coremanager_listview.xml +++ b/android/phoenix/res/layout/coremanager_listview.xml @@ -1,5 +1,7 @@ Load Core Load Game Load Game (History) - Manage Cores Settings Help About @@ -33,6 +32,14 @@ Successfully uninstalled core: %1$s. Downloadable Cores + + Display Name + Internal Name + Emulated Systems + Emulator Authors + Core License + Manufacturer + Refresh rate calibration Touch the screen with your fingers for more accurate measurements. @@ -51,7 +58,7 @@ Welcome to RetroArch This is your first time starting up RetroArch. RetroArch will now be preconfigured for the best possible gameplay experience. GPL waiver - 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. + Copyright © 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. 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. 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. NVidia Shield detected diff --git a/android/phoenix/src/com/retroarch/browser/ModuleWrapper.java b/android/phoenix/src/com/retroarch/browser/ModuleWrapper.java index 03a118419f..7a4aa1eea4 100644 --- a/android/phoenix/src/com/retroarch/browser/ModuleWrapper.java +++ b/android/phoenix/src/com/retroarch/browser/ModuleWrapper.java @@ -22,6 +22,7 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable authors; private final List supportedExtensions; /** @@ -54,29 +55,40 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable(Arrays.asList(supportedExts.split("|"))); + this.supportedExtensions = new ArrayList(Arrays.asList(supportedExts.split("\\|"))); } else { - this.supportedExtensions = new ArrayList(); + this.supportedExtensions = new ArrayList(); this.supportedExtensions.add(supportedExts); } + + final String emuAuthors = infoFile.getString("authors"); + if (emuAuthors != null && emuAuthors.contains("|")) + { + this.authors = new ArrayList(Arrays.asList(emuAuthors.split("\\|"))); + } + else + { + this.authors = new ArrayList(); + this.authors.add(emuAuthors); + } } else // No info file. { @@ -84,6 +96,7 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable(); this.supportedExtensions = new ArrayList(); this.coreName = coreName; } @@ -109,6 +122,26 @@ public final class ModuleWrapper implements IconAdapterItem, Comparable getEmulatorAuthors() + { + return authors; + } + /** * Gets the List of supported extensions for this core. * diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java b/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java index d7e9019487..baec77da64 100644 --- a/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java +++ b/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java @@ -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 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(); diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresFragment.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresFragment.java index 7ee0fb233e..281c9c1dea 100644 --- a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresFragment.java +++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresFragment.java @@ -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. } diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoFragment.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoFragment.java new file mode 100644 index 0000000000..b857bef9c7 --- /dev/null +++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoFragment.java @@ -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 + { + 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; + } + } +} diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoItem.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoItem.java new file mode 100644 index 0000000000..6b6d4ae0d8 --- /dev/null +++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoreInfoItem.java @@ -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. + *

+ * 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 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; + } +} diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresFragment.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresFragment.java index 6446930e42..75c9d79fbc 100644 --- a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresFragment.java +++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresFragment.java @@ -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 + *

+ * 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 items = new ArrayList(); @@ -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; diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java new file mode 100644 index 0000000000..9d2a6a9947 --- /dev/null +++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java @@ -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(); + } + } +}