diff --git a/android/phoenix/.classpath b/android/phoenix/.classpath
index 51769745b2..08152dcbe2 100644
--- a/android/phoenix/.classpath
+++ b/android/phoenix/.classpath
@@ -5,5 +5,6 @@
+
diff --git a/android/phoenix/libs/jsoup-1.8.1.jar b/android/phoenix/libs/jsoup-1.8.1.jar
new file mode 100644
index 0000000000..ae717d450e
Binary files /dev/null and b/android/phoenix/libs/jsoup-1.8.1.jar differ
diff --git a/android/phoenix/res/values/strings.xml b/android/phoenix/res/values/strings.xml
index f8907d16c2..e78f00f710 100644
--- a/android/phoenix/res/values/strings.xml
+++ b/android/phoenix/res/values/strings.xml
@@ -40,6 +40,11 @@
Manufacturer
Permissions
+
+ Confirm
+ Are you sure you want to download %1$s?
+ Downloading %1$s…
+
Refresh rate calibration
Touch the screen with your fingers for more accurate measurements.
diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java b/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java
index 9bb1a5e33a..efb2321714 100644
--- a/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java
+++ b/android/phoenix/src/com/retroarch/browser/coremanager/CoreManagerActivity.java
@@ -4,6 +4,7 @@ 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;
@@ -17,7 +18,7 @@ import android.support.v7.app.ActionBarActivity;
* Activity which provides the base for viewing installed cores,
* as well as the ability to download other cores.
*/
-public final class CoreManagerActivity extends ActionBarActivity
+public final class CoreManagerActivity extends ActionBarActivity implements DownloadableCoresFragment.OnCoreDownloadedListener
{
@Override
public void onCreate(Bundle savedInstanceState)
@@ -76,6 +77,19 @@ public final class CoreManagerActivity extends ActionBarActivity
return false;
}
+ // Callback function used to update the installed cores list
+ @Override
+ public void onCoreDownloaded()
+ {
+ InstalledCoresManagerFragment icmf = (InstalledCoresManagerFragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.coreviewer_viewPager + ":" + 0);
+ if (icmf != null)
+ {
+ InstalledCoresFragment icf = (InstalledCoresFragment) icmf.getChildFragmentManager().findFragmentByTag("InstalledCoresList");
+ if (icf != null)
+ icf.updateInstalledCoresList();
+ }
+ }
+
// Adapter for the core manager ViewPager.
private final class ViewPagerAdapter extends FragmentPagerAdapter
{
diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCore.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCore.java
new file mode 100644
index 0000000000..9e55b96ccb
--- /dev/null
+++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCore.java
@@ -0,0 +1,57 @@
+package com.retroarch.browser.coremanager.fragments;
+
+/**
+ * Represents a core that can be downloaded.
+ */
+final class DownloadableCore
+{
+ private final String coreName;
+ private final String coreURL;
+ private final String shortURL;
+
+ /**
+ * Constructor
+ *
+ * @param coreName Name of the core.
+ * @param coreURL URL to this core.
+ */
+ public DownloadableCore(String coreName, String coreURL)
+ {
+ this.coreName = coreName;
+ this.coreURL = coreURL;
+ this.shortURL = coreURL.substring(coreURL.lastIndexOf('/') + 1);
+ }
+
+ /**
+ * Gets the name of this core.
+ *
+ * @return The name of this core.
+ */
+ public String getCoreName()
+ {
+ return coreName;
+ }
+
+ /**
+ * Gets the URL to download this core.
+ *
+ * @return The URL to download this core.
+ */
+ public String getCoreURL()
+ {
+ return coreURL;
+ }
+
+ /**
+ * Gets the short URL name of this core.
+ *
+ * e.g. Consider the url: www.somesite/somecore.zip.
+ * This would return "somecore.zip"
+ *
+ * @return the short URL name of this core.
+ */
+ public String getShortURLName()
+ {
+ return shortURL;
+ }
+}
diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresAdapter.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresAdapter.java
new file mode 100644
index 0000000000..63e63f5158
--- /dev/null
+++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresAdapter.java
@@ -0,0 +1,54 @@
+package com.retroarch.browser.coremanager.fragments;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+/**
+ * {@link ArrayAdapter} that handles the display of downloadable cores.
+ */
+final class DownloadableCoresAdapter extends ArrayAdapter
+{
+ private final int layoutId;
+
+ /**
+ * Constructor
+ *
+ * @param context The current {@link Context}.
+ * @param layoutID The resource ID for a layout file containing a layout to use when instantiating views
+ */
+ public DownloadableCoresAdapter(Context context, int layoutId)
+ {
+ super(context, layoutId);
+
+ this.layoutId = layoutId;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ if (convertView == null)
+ {
+ final LayoutInflater li = LayoutInflater.from(getContext());
+ convertView = li.inflate(layoutId, parent, false);
+ }
+
+ final DownloadableCore core = getItem(position);
+ if (core != null)
+ {
+ TextView title = (TextView) convertView.findViewById(android.R.id.text1);
+ TextView subtitle = (TextView) convertView.findViewById(android.R.id.text2);
+
+ if (title != null)
+ title.setText(core.getCoreName());
+
+ if (subtitle != null)
+ subtitle.setText(core.getCoreURL());
+ }
+
+ return convertView;
+ }
+}
\ No newline at end of file
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 19fd4cdf46..9b91117ce7 100644
--- a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresFragment.java
+++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/DownloadableCoresFragment.java
@@ -1,27 +1,378 @@
package com.retroarch.browser.coremanager.fragments;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.jsoup.Connection;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncTask;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.support.v4.app.ListFragment;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
+import android.widget.ListView;
+
+import com.retroarch.R;
/**
* {@link ListFragment} that is responsible for showing
* cores that are able to be downloaded or are not installed.
*/
-public final class DownloadableCoresFragment extends Fragment
+public final class DownloadableCoresFragment extends ListFragment
{
- // TODO: Implement complete functionality.
+ // List of TODOs.
+ // - Eventually make the core downloader capable of directory-based browsing from the base URL.
+ // - Allow for 'repository'-like core downloading.
+ // - Clean this up a little better. It can likely be way more organized.
+ // - Don't re-download the info files on orientation changes.
+ // - Use a loading wheel when core retrieval is being done. User may think something went wrong otherwise.
+ // - Check the info directory for an info file before downloading it. Can save bandwidth this way (and list load times would be faster).
+ // - Should probably display a dialog or a toast message when the Internet connection process fails.
+
+ /**
+ * Dictates what actions will occur when a core download completes.
+ *
+ * Acts like a callback so that communication between fragments is possible.
+ */
+ public interface OnCoreDownloadedListener
+ {
+ /** The action that will occur when a core is successfully downloaded. */
+ void onCoreDownloaded();
+ }
+
+ private static final String BUILDBOT_BASE_URL = "http://buildbot.libretro.com";
+ private static final String BUILDBOT_CORE_URL = BUILDBOT_BASE_URL + "/stable/android/armv7/1.0.0.2/cores/";
+ private static final String BUILDBOT_INFO_URL = BUILDBOT_BASE_URL + "/stable/android/armv7/1.0.0.2/info/";
+
+ private OnCoreDownloadedListener coreDownloadedListener = null;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
- TextView tv = new TextView(getActivity());
- tv.setText("In development, just a little longer!");
+ super.onCreateView(inflater, container, savedInstanceState);
+ final ListView coreList = (ListView) inflater.inflate(R.layout.coremanager_listview, container, false);
- return tv;
+ final DownloadableCoresAdapter adapter = new DownloadableCoresAdapter(getActivity(), android.R.layout.simple_list_item_2);
+ adapter.setNotifyOnChange(true);
+ coreList.setAdapter(adapter);
+
+ coreDownloadedListener = (OnCoreDownloadedListener) getActivity();
+
+ new PopulateCoresListOperation(adapter).execute();
+
+ return coreList;
+ }
+
+ @Override
+ public void onListItemClick(final ListView lv, final View v, final int position, final long id)
+ {
+ super.onListItemClick(lv, v, position, id);
+ final DownloadableCore core = (DownloadableCore) lv.getItemAtPosition(position);
+
+ // Prompt the user for confirmation on downloading the core.
+ AlertDialog.Builder notification = new AlertDialog.Builder(getActivity());
+ notification.setMessage(String.format(getString(R.string.download_core_confirm_msg), core.getCoreName()));
+ notification.setTitle(R.string.download_core_confirm_title);
+ notification.setNegativeButton(R.string.no, null);
+ notification.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ // Begin downloading the core.
+ new DownloadCoreOperation(getActivity(), core.getCoreName()).execute(core.getCoreURL(), core.getShortURLName());
+ }
+ });
+ notification.show();
+ }
+
+ // Async event responsible for populating the Downloadable Cores list.
+ private static final class PopulateCoresListOperation extends AsyncTask>
+ {
+ // Acts as an object reference to an adapter in a list view.
+ private DownloadableCoresAdapter adapter;
+
+ /**
+ * Constructor
+ *
+ * @param adapter The adapter to asynchronously update.
+ */
+ public PopulateCoresListOperation(DownloadableCoresAdapter adapter)
+ {
+ this.adapter = adapter;
+ }
+
+ @Override
+ protected ArrayList doInBackground(Void... params)
+ {
+ try
+ {
+ final Connection core_connection = Jsoup.connect(BUILDBOT_CORE_URL);
+ final Elements coreElements = core_connection.get().body().getElementsByClass("fb-n").select("a");
+
+ final ArrayList downloadableCores = new ArrayList();
+
+ // NOTE: Start from 1 to skip the ".." (parent directory element)
+ // Set this to zero if directory-based browsing becomes a thing.
+ for (int i = 1; i < coreElements.size(); i++)
+ {
+ Element coreElement = coreElements.get(i);
+
+ final String coreURL = BUILDBOT_BASE_URL + coreElement.attr("href");
+ final String coreName = coreURL.substring(coreURL.lastIndexOf("/") + 1);
+ final String infoURL = BUILDBOT_INFO_URL + coreName.replace("_android.so.zip", ".info");
+
+ downloadableCores.add(new DownloadableCore(getCoreName(infoURL), coreURL));
+ }
+
+ return downloadableCores;
+ }
+ catch (IOException e)
+ {
+ Log.e("PopulateCoresListOperation", e.getMessage());
+
+ // Make a dummy entry to notify an error.
+ final ArrayList errorList = new ArrayList();
+ errorList.add(new DownloadableCore("Error", e.getMessage()));
+ return errorList;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(ArrayList result)
+ {
+ super.onPostExecute(result);
+ adapter.addAll(result);
+ }
+
+ // Literally downloads the info file, writes it, and parses it for the corename key/value pair.
+ // AKA an argument for having a manifest file on the server.
+ //
+ // This makes list loading take way longer than it should.
+ //
+ // One way this can be improved is by checking the info directory for
+ // existing info files that match the core. Eliminating the download retrieval.
+ private String getCoreName(String urlPath) throws IOException
+ {
+ final URL url = new URL(urlPath);
+ final BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
+ final StringBuilder sb = new StringBuilder();
+
+ String str = "";
+ while ((str = br.readLine()) != null)
+ sb.append(str + "\n");
+ br.close();
+
+ // Write the info file.
+ File outputPath = new File(adapter.getContext().getApplicationInfo().dataDir + "/info/", urlPath.substring(urlPath.lastIndexOf('/') + 1));
+ BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath));
+ bw.append(sb);
+ bw.close();
+
+ // Now read the core name
+ String[] lines = sb.toString().split("\n");
+ String name = "";
+ for (int i = 0; i < lines.length; i++)
+ {
+ if (lines[i].contains("corename"))
+ {
+ // Gross
+ name = lines[i].split("=")[1].trim().replace("\"", "");
+ break;
+ }
+ }
+
+ return name;
+ }
+ }
+
+ // Executed when the user confirms a core download.
+ private final class DownloadCoreOperation extends AsyncTask
+ {
+ private final ProgressDialog dlg;
+ private final Context ctx;
+ private final String coreName;
+
+ /**
+ * Constructor
+ *
+ * @param ctx The current {@link Context}.
+ * @param coreName The name of the core being downloaded.
+ */
+ public DownloadCoreOperation(Context ctx, String coreName)
+ {
+ this.dlg = new ProgressDialog(ctx);
+ this.ctx = ctx;
+ this.coreName = coreName;
+ }
+
+ @Override
+ protected void onPreExecute()
+ {
+ super.onPreExecute();
+
+ dlg.setMessage(String.format(ctx.getString(R.string.downloading_msg), coreName));
+ dlg.setCancelable(false);
+ dlg.setCanceledOnTouchOutside(false);
+ dlg.setIndeterminate(false);
+ dlg.setMax(100);
+ dlg.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ dlg.show();
+ }
+
+ @Override
+ protected Void doInBackground(String... params)
+ {
+ InputStream input = null;
+ OutputStream output = null;
+ HttpURLConnection connection = null;
+ try
+ {
+ URL url = new URL(params[0]);
+ connection = (HttpURLConnection) url.openConnection();
+ connection.connect();
+
+ if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
+ {
+ Log.i("DownloadCoreOperation", "HTTP response code not OK. Response code: " + connection.getResponseCode());
+ return null;
+ }
+
+ // Set up the streams
+ final int fileLen = connection.getContentLength();
+ final File zipPath = new File(ctx.getApplicationInfo().dataDir + "/cores/", params[1]);
+ input = new BufferedInputStream(connection.getInputStream(), 8192);
+ output = new FileOutputStream(zipPath);
+
+ // Download and write to storage.
+ long totalDownloaded = 0;
+ byte[] buffer = new byte[4096];
+ int countBytes = 0;
+ while ((countBytes = input.read(buffer)) != -1)
+ {
+ totalDownloaded += countBytes;
+ if (fileLen > 0)
+ publishProgress((int) (totalDownloaded * 100 / fileLen));
+
+ output.write(buffer, 0, countBytes);
+ }
+
+ unzipCore(zipPath);
+ }
+ catch (IOException ignored)
+ {
+ // Can't really do anything to recover.
+ }
+ finally
+ {
+ try
+ {
+ if (output != null)
+ output.close();
+
+ if (input != null)
+ input.close();
+ }
+ catch (IOException ignored)
+ {
+ }
+
+ if (connection != null)
+ connection.disconnect();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... progress)
+ {
+ super.onProgressUpdate(progress);
+
+ dlg.setProgress(progress[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Void result)
+ {
+ super.onPostExecute(result);
+
+ if (dlg.isShowing())
+ dlg.dismiss();
+
+ // Invoke callback to update the installed cores list.
+ coreDownloadedListener.onCoreDownloaded();
+ }
+ }
+
+ // Java 6 ladies and gentlemen.
+ private static void unzipCore(File zipFile)
+ {
+ ZipInputStream zis = null;
+
+ try
+ {
+ zis = new ZipInputStream(new FileInputStream(zipFile));
+ ZipEntry entry = zis.getNextEntry();
+
+ while (entry != null)
+ {
+ File file = new File(zipFile.getParent(), entry.getName());
+
+ FileOutputStream fos = new FileOutputStream(file);
+ int len = 0;
+ byte[] buffer = new byte[4096];
+ while ((len = zis.read(buffer)) != -1)
+ {
+ fos.write(buffer, 0, len);
+ }
+ fos.close();
+
+ entry = zis.getNextEntry();
+ }
+ }
+ catch (IOException ignored)
+ {
+ // Can't do anything.
+ }
+ finally
+ {
+ try
+ {
+ if (zis != null)
+ {
+ zis.closeEntry();
+ zis.close();
+ }
+ }
+ catch (IOException ignored)
+ {
+ // Can't do anything
+ }
+
+ zipFile.delete();
+ }
}
}
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 75c9d79fbc..1773fff3fb 100644
--- a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresFragment.java
+++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresFragment.java
@@ -56,70 +56,23 @@ public final class InstalledCoresFragment extends ListFragment
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ public void onActivityCreated(Bundle savedInstanceState)
{
- // Inflate the layout for this ListFragment.
- ListView parentView = (ListView) inflater.inflate(R.layout.coremanager_listview, container, false);
+ super.onActivityCreated(savedInstanceState);
- // Set the long click listener.
- parentView.setOnItemLongClickListener(itemLongClickListener);
+ adapter = new InstalledCoresAdapter(getActivity(), android.R.layout.simple_list_item_2, getInstalledCoresList());
+ setListAdapter(adapter);
// 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();
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState)
+ {
+ super.onViewCreated(view, savedInstanceState);
- // Check if the device supports NEON.
- final String cpuInfo = UserPreferences.readCPUInfo();
- final boolean supportsNeon = cpuInfo.contains("neon");
-
- // Populate the list
- final File[] libs = new File(getActivity().getApplicationInfo().dataDir, "/cores").listFiles();
- if (libs != null)
- {
- for (File lib : libs)
- {
- 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));
- }
- }
-
- // Sort the list alphabetically
- Collections.sort(items);
-
- // Initialize and set the backing adapter for this ListFragment.
- adapter = new InstalledCoresAdapter(getActivity(), android.R.layout.simple_list_item_2, items);
- parentView.setAdapter(adapter);
-
- return parentView;
+ getListView().setOnItemLongClickListener(itemLongClickListener);
}
@Override
@@ -131,6 +84,68 @@ public final class InstalledCoresFragment extends ListFragment
getListView().setItemChecked(position, true);
}
+ /**
+ * Refreshes the list of installed cores.
+ */
+ public void updateInstalledCoresList()
+ {
+ adapter.clear();
+ adapter.addAll(getInstalledCoresList());
+ adapter.notifyDataSetChanged();
+ }
+
+ private List getInstalledCoresList()
+ {
+ // The list of items that will be added to the adapter backing this ListFragment.
+ final List items = new ArrayList();
+
+ // Check if the device supports NEON.
+ final String cpuInfo = UserPreferences.readCPUInfo();
+ final boolean supportsNeon = cpuInfo.contains("neon");
+
+ // Populate the list
+ final File[] libs = new File(getActivity().getApplicationInfo().dataDir, "/cores").listFiles();
+ if (libs != null)
+ {
+ for (File lib : libs)
+ {
+ 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));
+ }
+ }
+
+ // Sort the list alphabetically
+ Collections.sort(items);
+ return items;
+ }
+
// This will be the handler for long clicks on individual list items in this ListFragment.
private final OnItemLongClickListener itemLongClickListener = new OnItemLongClickListener()
{
diff --git a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java
index 9d2a6a9947..1178710130 100644
--- a/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java
+++ b/android/phoenix/src/com/retroarch/browser/coremanager/fragments/InstalledCoresManagerFragment.java
@@ -23,7 +23,7 @@ public class InstalledCoresManagerFragment extends Fragment implements Installed
final Fragment installedCores = new InstalledCoresFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
- ft.replace(R.id.installed_cores_fragment_container1, installedCores);
+ ft.replace(R.id.installed_cores_fragment_container1, installedCores, "InstalledCoresList");
ft.commit();
return v;
diff --git a/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java b/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java
index 9d543eaf66..603af52583 100644
--- a/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java
+++ b/android/phoenix/src/com/retroarch/browser/mainmenu/MainMenuActivity.java
@@ -1,9 +1,12 @@
package com.retroarch.browser.mainmenu;
+import java.io.File;
+
import com.retroarch.R;
import com.retroarch.browser.preferences.util.UserPreferences;
import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
import android.media.AudioManager;
import android.os.Bundle;
import android.preference.PreferenceActivity;
@@ -21,6 +24,15 @@ public final class MainMenuActivity extends FragmentActivity
{
super.onCreate(savedInstanceState);
+ // Ensure resource directories are created.
+ final ApplicationInfo info = getApplicationInfo();
+ final File coresDir = new File(info.dataDir, "cores");
+ final File infoDir = new File(info.dataDir, "info");
+ if (!coresDir.exists())
+ coresDir.mkdir();
+ if (!infoDir.exists())
+ infoDir.mkdir();
+
// Load the main menu layout
setContentView(R.layout.mainmenu_activity_layout);
if (savedInstanceState == null)