summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorEder Bastos <bastos.eder@gmail.com>2015-04-30 18:15:10 -0400
committerEder Bastos <bastos.eder@gmail.com>2015-05-02 21:49:17 -0400
commitc80225ea4d023ed91503521b74951db3a302ca20 (patch)
treed69c8493aedf1226b9d4e9b447ef09364aaace84 /Source/Android/app/src/main/java
parent4b00ccbe4ed79a3fd3369ca1b2972f0b8e4224ef (diff)
Convert the Android source code to the directory structure of a Gradle-based Android Studio project.
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/AssetCopyService.java115
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java213
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutActivity.java122
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutFragmentItem.java48
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutInfoFragmentAdapter.java67
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/CPUInfoFragment.java52
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/DolphinInfoFragment.java48
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES2InfoFragment.java82
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES3InfoFragment.java114
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLInfoFragment.java69
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/Limit.java71
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/EmulationActivity.java316
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/NativeGLSurfaceView.java74
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java288
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java45
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java137
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowser.java159
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserAdapter.java112
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserItem.java110
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListActivity.java374
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListAdapter.java124
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListFragment.java150
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListItem.java102
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/PrefsActivity.java101
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/UserPreferences.java284
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/cpu/CPUSettingsFragment.java56
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/custom/UpdatingListPreference.java39
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputBindingPreference.java55
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputConfigFragment.java102
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/MotionAlertDialog.java127
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigActivity.java51
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigButton.java166
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/video/VideoSettingsFragment.java202
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuAdapter.java75
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuItem.java50
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CPUHelper.java395
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/EGLHelper.java393
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java94
38 files changed, 5182 insertions, 0 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/AssetCopyService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/AssetCopyService.java
new file mode 100644
index 0000000000..d39bbec59f
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/AssetCopyService.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.Environment;
+import android.util.Log;
+
+import org.dolphinemu.dolphinemu.settings.UserPreferences;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A service that spawns its own thread in order to copy several binary and shader files
+ * from the Dolphin APK to the external file system.
+ */
+public final class AssetCopyService extends IntentService
+{
+ private static final String TAG = "DolphinEmulator";
+
+ public AssetCopyService()
+ {
+ // Superclass constructor is called to name the thread on which this service executes.
+ super("AssetCopyService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent)
+ {
+ String BaseDir = NativeLibrary.GetUserDirectory();
+ String ConfigDir = BaseDir + File.separator + "Config";
+ String GCDir = BaseDir + File.separator + "GC";
+
+ // Copy assets if needed
+ File file = new File(GCDir + File.separator + "font_sjis.bin");
+ if(!file.exists())
+ {
+ NativeLibrary.CreateUserFolders();
+ copyAsset("dsp_coef.bin", GCDir + File.separator + "dsp_coef.bin");
+ copyAsset("dsp_rom.bin", GCDir + File.separator + "dsp_rom.bin");
+ copyAsset("font_ansi.bin", GCDir + File.separator + "font_ansi.bin");
+ copyAsset("font_sjis.bin", GCDir + File.separator + "font_sjis.bin");
+ copyAssetFolder("Shaders", BaseDir + File.separator + "Shaders");
+ }
+ else
+ {
+ Log.v(TAG, "Skipping asset copy operation.");
+ }
+
+ // Always copy over the GCPad config in case of change or corruption.
+ // Not a user configurable file.
+ copyAsset("GCPadNew.ini", ConfigDir + File.separator + "GCPadNew.ini");
+
+ // Load the configuration keys set in the Dolphin ini and gfx ini files
+ // into the application's shared preferences.
+ UserPreferences.LoadIniToPrefs(this);
+ }
+
+ private void copyAsset(String asset, String output)
+ {
+ Log.v(TAG, "Copying " + asset + " to " + output);
+ InputStream in = null;
+ OutputStream out = null;
+
+ try
+ {
+ in = getAssets().open(asset);
+ out = new FileOutputStream(output);
+ copyFile(in, out);
+ in.close();
+ out.close();
+ }
+ catch (IOException e)
+ {
+ Log.e(TAG, "Failed to copy asset file: " + asset, e);
+ }
+ }
+
+ private void copyAssetFolder(String assetFolder, String outputFolder)
+ {
+ Log.v(TAG, "Copying " + assetFolder + " to " + outputFolder);
+
+ try
+ {
+ for (String file : getAssets().list(assetFolder))
+ {
+ copyAsset(assetFolder + File.separator + file, outputFolder + File.separator + file);
+ }
+ }
+ catch (IOException e)
+ {
+ Log.e(TAG, "Failed to copy asset folder: " + assetFolder, e);
+ }
+ }
+
+ private void copyFile(InputStream in, OutputStream out) throws IOException
+ {
+ byte[] buffer = new byte[1024];
+ int read;
+
+ while ((read = in.read(buffer)) != -1)
+ {
+ out.write(buffer, 0, read);
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
new file mode 100644
index 0000000000..ab9f25b306
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu;
+
+import android.util.Log;
+import android.view.Surface;
+
+/**
+ * Class which contains methods that interact
+ * with the native side of the Dolphin code.
+ */
+public final class NativeLibrary
+{
+ /**
+ * Button type for use in onTouchEvent
+ */
+ public static final class ButtonType
+ {
+ public static final int BUTTON_A = 0;
+ public static final int BUTTON_B = 1;
+ public static final int BUTTON_START = 2;
+ public static final int BUTTON_X = 3;
+ public static final int BUTTON_Y = 4;
+ public static final int BUTTON_Z = 5;
+ public static final int BUTTON_UP = 6;
+ public static final int BUTTON_DOWN = 7;
+ public static final int BUTTON_LEFT = 8;
+ public static final int BUTTON_RIGHT = 9;
+ public static final int STICK_MAIN = 10;
+ public static final int STICK_MAIN_UP = 11;
+ public static final int STICK_MAIN_DOWN = 12;
+ public static final int STICK_MAIN_LEFT = 13;
+ public static final int STICK_MAIN_RIGHT = 14;
+ public static final int STICK_C = 15;
+ public static final int STICK_C_UP = 16;
+ public static final int STICK_C_DOWN = 17;
+ public static final int STICK_C_LEFT = 18;
+ public static final int STICK_C_RIGHT = 19;
+ public static final int TRIGGER_L = 20;
+ public static final int TRIGGER_R = 21;
+ }
+
+ /**
+ * Button states
+ */
+ public static final class ButtonState
+ {
+ public static final int RELEASED = 0;
+ public static final int PRESSED = 1;
+ }
+
+ private NativeLibrary()
+ {
+ // Disallows instantiation.
+ }
+
+ /**
+ * Default touchscreen device
+ */
+ public static final String TouchScreenDevice = "Touchscreen";
+
+ /**
+ * Handles button press events for a gamepad.
+ *
+ * @param Device The input descriptor of the gamepad.
+ * @param Button Key code identifying which button was pressed.
+ * @param Action Mask identifying which action is happing (button pressed down, or button released).
+ *
+ * @return If we handled the button press.
+ */
+ public static native boolean onGamePadEvent(String Device, int Button, int Action);
+
+ /**
+ * Handles gamepad movement events.
+ *
+ * @param Device The device ID of the gamepad.
+ * @param Axis The axis ID
+ * @param Value The value of the axis represented by the given ID.
+ */
+ public static native void onGamePadMoveEvent(String Device, int Axis, float Value);
+
+ /**
+ * Gets a value from a key in the given ini-based config file.
+ *
+ * @param configFile The ini-based config file to get the value from.
+ * @param Section The section key that the actual key is in.
+ * @param Key The key to get the value from.
+ * @param Default The value to return in the event the given key doesn't exist.
+ *
+ * @return the value stored at the key, or a default value if it doesn't exist.
+ */
+ public static native String GetConfig(String configFile, String Section, String Key, String Default);
+
+ /**
+ * Sets a value to a key in the given ini config file.
+ *
+ * @param configFile The ini-based config file to add the value to.
+ * @param Section The section key for the ini key
+ * @param Key The actual ini key to set.
+ * @param Value The string to set the ini key to.
+ */
+ public static native void SetConfig(String configFile, String Section, String Key, String Value);
+
+ /**
+ * Sets the filename to be run during emulation.
+ *
+ * @param filename The filename to be run during emulation.
+ */
+ public static native void SetFilename(String filename);
+
+ /**
+ * Gets the embedded banner within the given ISO/ROM.
+ *
+ * @param filename the file path to the ISO/ROM.
+ *
+ * @return an integer array containing the color data for the banner.
+ */
+ public static native int[] GetBanner(String filename);
+
+ /**
+ * Gets the embedded title of the given ISO/ROM.
+ *
+ * @param filename The file path to the ISO/ROM.
+ *
+ * @return the embedded title of the ISO/ROM.
+ */
+ public static native String GetTitle(String filename);
+
+ /**
+ * Gets the Dolphin version string.
+ *
+ * @return the Dolphin version string.
+ */
+ public static native String GetVersionString();
+
+ /**
+ * Returns if the phone supports NEON or not
+ *
+ * @return true if it supports NEON, false otherwise.
+ */
+ public static native boolean SupportsNEON();
+
+ /**
+ * Saves a screen capture of the game
+ *
+ */
+ public static native void SaveScreenShot();
+
+ /**
+ * Saves a game state to the slot number.
+ *
+ * @param slot The slot location to save state to.
+ */
+ public static native void SaveState(int slot);
+
+ /**
+ * Loads a game state from the slot number.
+ *
+ * @param slot The slot location to load state from.
+ */
+ public static native void LoadState(int slot);
+
+ /**
+ * Creates the initial folder structure in /sdcard/dolphin-emu/
+ */
+ public static native void CreateUserFolders();
+
+ /**
+ * Sets the current working user directory
+ * If not set, it auto-detects a location
+ */
+ public static native void SetUserDirectory(String directory);
+
+ /**
+ * Returns the current working user directory
+ */
+ public static native String GetUserDirectory();
+
+ /**
+ * Begins emulation.
+ *
+ * @param surf The surface to render to.
+ */
+ public static native void Run(Surface surf);
+
+ /** Unpauses emulation from a paused state. */
+ public static native void UnPauseEmulation();
+
+ /** Pauses emulation. */
+ public static native void PauseEmulation();
+
+ /** Stops emulation. */
+ public static native void StopEmulation();
+
+ /** Native EGL functions not exposed by Java bindings **/
+ public static native void eglBindAPI(int api);
+
+ static
+ {
+ try
+ {
+ System.loadLibrary("main");
+ }
+ catch (UnsatisfiedLinkError ex)
+ {
+ Log.e("NativeLibrary", ex.toString());
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutActivity.java
new file mode 100644
index 0000000000..33b30d9a43
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutActivity.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+
+
+/**
+ * Activity for the about menu, which displays info
+ * related to the CPU and GPU. Along with misc other info.
+ */
+public final class AboutActivity extends Activity
+{
+ private final EGLHelper eglHelper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // Set the view pager
+ setContentView(R.layout.viewpager);
+
+ // Initialize the viewpager.
+ final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
+ viewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+
+ eglHelper.closeHelper();
+ }
+
+ /**
+ * {@link FragmentPagerAdapter} subclass responsible for handling
+ * the individual {@link Fragment}s within the {@link ViewPager}.
+ */
+ private final class ViewPagerAdapter extends FragmentPagerAdapter
+ {
+ private final String[] pageTitles = {
+ getString(R.string.general),
+ getString(R.string.cpu),
+ getString(R.string.gles_two),
+ getString(R.string.gles_three),
+ getString(R.string.desktop_gl),
+ };
+
+ public ViewPagerAdapter(FragmentManager fm)
+ {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position)
+ {
+ if (position == 0)
+ {
+ return new DolphinInfoFragment();
+ }
+ else if (position == 1)
+ {
+ return new CPUInfoFragment(); // CPU
+ }
+ else if (position == 2) // GLES 2
+ {
+ return new GLES2InfoFragment();
+ }
+ else if (position == 3) // GLES 3 or OpenGL (depending on circumstances)
+ {
+ if (eglHelper.supportsGLES3())
+ return new GLES3InfoFragment();
+ else
+ return new GLInfoFragment(); // GLES3 not supported, but OpenGL is.
+ }
+ else if (position == 4) // OpenGL fragment
+ {
+ return new GLInfoFragment();
+ }
+
+ // This should never happen.
+ return null;
+ }
+
+ @Override
+ public int getCount()
+ {
+ if (eglHelper.supportsGLES3() && eglHelper.supportsOpenGL())
+ {
+ return 5;
+ }
+ else if (!eglHelper.supportsGLES3() && !eglHelper.supportsOpenGL())
+ {
+ return 3;
+ }
+ else // Either regular OpenGL or GLES3 isn't supported
+ {
+ return 4;
+ }
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position)
+ {
+ return pageTitles[position];
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutFragmentItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutFragmentItem.java
new file mode 100644
index 0000000000..ffc6c51c5a
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutFragmentItem.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+/**
+ * Represents an item within an info fragment in the About menu.
+ */
+final class AboutFragmentItem
+{
+ private final String title;
+ private final String subtitle;
+
+ /**
+ * Constructor
+ *
+ * @param title The title of this item.
+ * @param subtitle The subtitle for this item.
+ */
+ public AboutFragmentItem(String title, String subtitle)
+ {
+ this.title = title;
+ this.subtitle = subtitle;
+ }
+
+ /**
+ * Gets the title of this item.
+ *
+ * @return the title of this item.
+ */
+ public String getTitle()
+ {
+ return title;
+ }
+
+ /**
+ * Gets the subtitle of this item.
+ *
+ * @return the subtitle of this item.
+ */
+ public String getSubTitle()
+ {
+ return subtitle;
+ }
+} \ No newline at end of file
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutInfoFragmentAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutInfoFragmentAdapter.java
new file mode 100644
index 0000000000..abf2fd26cc
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/AboutInfoFragmentAdapter.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import java.util.List;
+
+import org.dolphinemu.dolphinemu.R;
+
+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} subclass specifically for the
+ * information fragments within the about menu.
+ */
+final class AboutInfoFragmentAdapter extends ArrayAdapter<AboutFragmentItem>
+{
+ private final int id;
+ private final List<AboutFragmentItem> items;
+
+ public AboutInfoFragmentAdapter(Context ctx, int id, List<AboutFragmentItem> items)
+ {
+ super(ctx, id, items);
+
+ this.id = id;
+ this.items = items;
+ }
+
+ @Override
+ public AboutFragmentItem getItem(int index)
+ {
+ return items.get(index);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ if (convertView == null)
+ {
+ LayoutInflater vi = LayoutInflater.from(getContext());
+ convertView = vi.inflate(id, parent, false);
+ }
+
+ final AboutFragmentItem item = items.get(position);
+ if (item != null)
+ {
+ TextView title = (TextView) convertView.findViewById(R.id.AboutItemTitle);
+ TextView subtitle = (TextView) convertView.findViewById(R.id.AboutItemSubTitle);
+
+ if (title != null)
+ title.setText(item.getTitle());
+
+ if (subtitle != null)
+ subtitle.setText(item.getSubTitle());
+ }
+
+ return convertView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/CPUInfoFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/CPUInfoFragment.java
new file mode 100644
index 0000000000..4183740c24
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/CPUInfoFragment.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.utils.CPUHelper;
+
+import android.app.ListFragment;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+/**
+ * {@link ListFragment class that is responsible
+ * for displaying information related to the CPU.
+ */
+public final class CPUInfoFragment extends ListFragment
+{
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ ListView rootView = (ListView) inflater.inflate(R.layout.gamelist_listview, container, false);
+ List<AboutFragmentItem> items = new ArrayList<AboutFragmentItem>();
+
+ CPUHelper cpuHelper = new CPUHelper(getActivity());
+
+ items.add(new AboutFragmentItem(getString(R.string.cpu_info), cpuHelper.getProcessorInfo()));
+ items.add(new AboutFragmentItem(getString(R.string.cpu_type), cpuHelper.getProcessorType()));
+ items.add(new AboutFragmentItem(getString(R.string.cpu_abi_one), Build.CPU_ABI));
+ items.add(new AboutFragmentItem(getString(R.string.cpu_abi_two), Build.CPU_ABI2));
+ items.add(new AboutFragmentItem(getString(R.string.num_cores), Integer.toString(cpuHelper.getNumCores())));
+ items.add(new AboutFragmentItem(getString(R.string.cpu_features), cpuHelper.getFeatures()));
+ items.add(new AboutFragmentItem(getString(R.string.cpu_hardware), Build.HARDWARE));
+ if (CPUHelper.isARM())
+ items.add(new AboutFragmentItem(getString(R.string.cpu_implementer), cpuHelper.getImplementer()));
+
+ AboutInfoFragmentAdapter adapter = new AboutInfoFragmentAdapter(getActivity(), R.layout.about_layout, items);
+ rootView.setAdapter(adapter);
+
+ return rootView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/DolphinInfoFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/DolphinInfoFragment.java
new file mode 100644
index 0000000000..69f8961f25
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/DolphinInfoFragment.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import android.app.ListFragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+/**
+ * Represents the about screen.
+ */
+public final class DolphinInfoFragment extends ListFragment
+{
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ ListView rootView = (ListView) inflater.inflate(R.layout.gamelist_listview, container, false);
+ EGLHelper eglHelper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT);
+
+ final String yes = getString(R.string.yes);
+ final String no = getString(R.string.no);
+
+ List<AboutFragmentItem> Input = new ArrayList<AboutFragmentItem>();
+ Input.add(new AboutFragmentItem(getString(R.string.build_revision), NativeLibrary.GetVersionString()));
+ Input.add(new AboutFragmentItem(getString(R.string.supports_gles3), eglHelper.supportsGLES3() ? yes : no));
+ Input.add(new AboutFragmentItem(getString(R.string.supports_neon), NativeLibrary.SupportsNEON() ? yes : no));
+
+ AboutInfoFragmentAdapter adapter = new AboutInfoFragmentAdapter(getActivity(), R.layout.about_layout, Input);
+ rootView.setAdapter(adapter);
+ rootView.setEnabled(false); // Makes the list view non-clickable.
+
+ return rootView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES2InfoFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES2InfoFragment.java
new file mode 100644
index 0000000000..08784dbd7d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES2InfoFragment.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import android.app.ListFragment;
+import android.opengl.GLES20;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.about.Limit.Type;
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link ListFragment} responsible for displaying
+ * information relating to OpenGL ES 2.
+ */
+public final class GLES2InfoFragment extends ListFragment
+{
+ private final Limit[] Limits = {
+ new Limit("Vendor", GLES20.GL_VENDOR, Type.STRING),
+ new Limit("Version", GLES20.GL_VERSION, Type.STRING),
+ new Limit("Renderer", GLES20.GL_RENDERER, Type.STRING),
+ new Limit("GLSL version", GLES20.GL_SHADING_LANGUAGE_VERSION, Type.STRING),
+ // GLES 2.0 Limits
+ new Limit("Aliased Point Size", GLES20.GL_ALIASED_POINT_SIZE_RANGE, Type.INTEGER_RANGE),
+ new Limit("Aliased Line Width ", GLES20.GL_ALIASED_LINE_WIDTH_RANGE, Type.INTEGER_RANGE),
+ new Limit("Max Texture Size", GLES20.GL_MAX_TEXTURE_SIZE, Type.INTEGER),
+ new Limit("Viewport Dimensions", GLES20.GL_MAX_VIEWPORT_DIMS, Type.INTEGER_RANGE),
+ new Limit("Subpixel Bits", GLES20.GL_SUBPIXEL_BITS, Type.INTEGER),
+ new Limit("Max Vertex Attributes", GLES20.GL_MAX_VERTEX_ATTRIBS, Type.INTEGER),
+ new Limit("Max Vertex Uniform Vectors", GLES20.GL_MAX_VERTEX_UNIFORM_VECTORS, Type.INTEGER),
+ new Limit("Max Varying Vectors", GLES20.GL_MAX_VARYING_VECTORS, Type.INTEGER),
+ new Limit("Max Combined Texture Units", GLES20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, Type.INTEGER),
+ new Limit("Max Vertex Texture Units", GLES20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, Type.INTEGER),
+ new Limit("Max Texture Units", GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, Type.INTEGER),
+ new Limit("Max Fragment Uniform Vectors", GLES20.GL_MAX_FRAGMENT_UNIFORM_VECTORS, Type.INTEGER),
+ new Limit("Max Cubemap Texture Size", GLES20.GL_MAX_CUBE_MAP_TEXTURE_SIZE, Type.INTEGER),
+ new Limit("Shader Binary Formats", GLES20.GL_NUM_SHADER_BINARY_FORMATS, Type.INTEGER),
+ new Limit("Max Framebuffer Size", GLES20.GL_MAX_RENDERBUFFER_SIZE, Type.INTEGER),
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ final EGLHelper eglHelper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT);
+
+ ListView rootView = (ListView) inflater.inflate(R.layout.gamelist_listview, container, false);
+ List<AboutFragmentItem> Input = new ArrayList<AboutFragmentItem>();
+
+ for (Limit limit : Limits)
+ {
+ Log.i("GLES2InfoFragment", "Getting enum " + limit.name);
+ Input.add(new AboutFragmentItem(limit.name, limit.GetValue(eglHelper)));
+ }
+
+ // Get extensions manually
+ String[] extensions = eglHelper.glGetString(GLES20.GL_EXTENSIONS).split(" ");
+ StringBuilder extensionsBuilder = new StringBuilder();
+ for (String extension : extensions)
+ {
+ extensionsBuilder.append(extension).append('\n');
+ }
+ Input.add(new AboutFragmentItem("OpenGL ES 2.0 Extensions", extensionsBuilder.toString()));
+
+ AboutInfoFragmentAdapter adapter = new AboutInfoFragmentAdapter(getActivity(), R.layout.about_layout, Input);
+ rootView.setAdapter(adapter);
+
+ return rootView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES3InfoFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES3InfoFragment.java
new file mode 100644
index 0000000000..2c4649fdfa
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLES3InfoFragment.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import android.app.ListFragment;
+import android.opengl.GLES30;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.about.Limit.Type;
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link ListFragment} responsible for displaying
+ * information relating to OpenGL ES 3.
+ */
+public final class GLES3InfoFragment extends ListFragment
+{
+ private final Limit[] Limits = {
+ new Limit("Vendor", GLES30.GL_VENDOR, Type.STRING),
+ new Limit("Version", GLES30.GL_VERSION, Type.STRING),
+ new Limit("Renderer", GLES30.GL_RENDERER, Type.STRING),
+ new Limit("GLSL version", GLES30.GL_SHADING_LANGUAGE_VERSION, Type.STRING),
+ // GLES 2.0 Limits
+ new Limit("Aliased Point Size", GLES30.GL_ALIASED_POINT_SIZE_RANGE, Type.INTEGER_RANGE),
+ new Limit("Aliased Line Width ", GLES30.GL_ALIASED_LINE_WIDTH_RANGE, Type.INTEGER_RANGE),
+ new Limit("Max Texture Size", GLES30.GL_MAX_TEXTURE_SIZE, Type.INTEGER),
+ new Limit("Viewport Dimensions", GLES30.GL_MAX_VIEWPORT_DIMS, Type.INTEGER_RANGE),
+ new Limit("Subpixel Bits", GLES30.GL_SUBPIXEL_BITS, Type.INTEGER),
+ new Limit("Max Vertex Attributes", GLES30.GL_MAX_VERTEX_ATTRIBS, Type.INTEGER),
+ new Limit("Max Vertex Uniform Vectors", GLES30.GL_MAX_VERTEX_UNIFORM_VECTORS, Type.INTEGER),
+ new Limit("Max Varying Vectors", GLES30.GL_MAX_VARYING_VECTORS, Type.INTEGER),
+ new Limit("Max Combined Texture Units", GLES30.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, Type.INTEGER),
+ new Limit("Max Vertex Texture Units", GLES30.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, Type.INTEGER),
+ new Limit("Max Texture Units", GLES30.GL_MAX_TEXTURE_IMAGE_UNITS, Type.INTEGER),
+ new Limit("Max Fragment Uniform Vectors", GLES30.GL_MAX_FRAGMENT_UNIFORM_VECTORS, Type.INTEGER),
+ new Limit("Max Cubemap Texture Size", GLES30.GL_MAX_CUBE_MAP_TEXTURE_SIZE, Type.INTEGER),
+ new Limit("Shader Binary Formats", GLES30.GL_NUM_SHADER_BINARY_FORMATS, Type.INTEGER),
+ new Limit("Max Framebuffer Size", GLES30.GL_MAX_RENDERBUFFER_SIZE, Type.INTEGER),
+ // GLES 3.0 limits
+ new Limit("Max 3D Texture size", GLES30.GL_MAX_3D_TEXTURE_SIZE, Type.INTEGER),
+ new Limit("Max Element Vertices", GLES30.GL_MAX_ELEMENTS_VERTICES, Type.INTEGER),
+ new Limit("Max Element Indices", GLES30.GL_MAX_ELEMENTS_INDICES, Type.INTEGER),
+ new Limit("Max Draw Buffers", GLES30.GL_MAX_DRAW_BUFFERS, Type.INTEGER),
+ new Limit("Max Fragment Uniform Components", GLES30.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, Type.INTEGER),
+ new Limit("Max Vertex Uniform Components", GLES30.GL_MAX_VERTEX_UNIFORM_COMPONENTS, Type.INTEGER),
+ new Limit("Number of Extensions", GLES30.GL_NUM_EXTENSIONS, Type.INTEGER),
+ new Limit("Max Array Texture Layers", GLES30.GL_MAX_ARRAY_TEXTURE_LAYERS, Type.INTEGER),
+ new Limit("Min Program Texel Offset", GLES30.GL_MIN_PROGRAM_TEXEL_OFFSET, Type.INTEGER),
+ new Limit("Max Program Texel Offset", GLES30.GL_MAX_PROGRAM_TEXEL_OFFSET, Type.INTEGER),
+ new Limit("Max Varying Components", GLES30.GL_MAX_VARYING_COMPONENTS, Type.INTEGER),
+ new Limit("Max TF Varying Length", GLES30.GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, Type.INTEGER),
+ new Limit("Max TF Separate Components", GLES30.GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, Type.INTEGER),
+ new Limit("Max TF Interleaved Components", GLES30.GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, Type.INTEGER),
+ new Limit("Max TF Separate Attributes", GLES30.GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, Type.INTEGER),
+ new Limit("Max Color Attachments", GLES30.GL_MAX_COLOR_ATTACHMENTS, Type.INTEGER),
+ new Limit("Max Samples", GLES30.GL_MAX_SAMPLES, Type.INTEGER),
+ new Limit("Max Vertex UBOs", GLES30.GL_MAX_VERTEX_UNIFORM_BLOCKS, Type.INTEGER),
+ new Limit("Max Fragment UBOs", GLES30.GL_MAX_FRAGMENT_UNIFORM_BLOCKS, Type.INTEGER),
+ new Limit("Max Combined UBOs", GLES30.GL_MAX_COMBINED_UNIFORM_BLOCKS, Type.INTEGER),
+ new Limit("Max Uniform Buffer Bindings", GLES30.GL_MAX_UNIFORM_BUFFER_BINDINGS, Type.INTEGER),
+ new Limit("Max UBO Size", GLES30.GL_MAX_UNIFORM_BLOCK_SIZE, Type.INTEGER),
+ new Limit("Max Combined Vertex Uniform Components", GLES30.GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, Type.INTEGER),
+ new Limit("Max Combined Fragment Uniform Components", GLES30.GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, Type.INTEGER),
+ new Limit("UBO Alignment", GLES30.GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, Type.INTEGER),
+ new Limit("Max Vertex Output Components", GLES30.GL_MAX_VERTEX_OUTPUT_COMPONENTS, Type.INTEGER),
+ new Limit("Max Fragment Input Components", GLES30.GL_MAX_FRAGMENT_INPUT_COMPONENTS, Type.INTEGER),
+ new Limit("Max Server Wait Timeout", GLES30.GL_MAX_SERVER_WAIT_TIMEOUT, Type.INTEGER),
+ new Limit("Program Binary Formats", GLES30.GL_NUM_PROGRAM_BINARY_FORMATS, Type.INTEGER),
+ new Limit("Max Element Index", GLES30.GL_MAX_ELEMENT_INDEX, Type.INTEGER),
+ new Limit("Sample Counts", GLES30.GL_NUM_SAMPLE_COUNTS, Type.INTEGER),
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ final EGLHelper eglHelper = new EGLHelper(EGLHelper.EGL_OPENGL_ES3_BIT_KHR);
+
+ ListView rootView = (ListView) inflater.inflate(R.layout.gamelist_listview, container, false);
+ List<AboutFragmentItem> Input = new ArrayList<AboutFragmentItem>();
+
+ for (Limit limit : Limits)
+ {
+ Log.i("GLES3InfoFragment", "Getting enum " + limit.name);
+ Input.add(new AboutFragmentItem(limit.name, limit.GetValue(eglHelper)));
+ }
+
+ // Get extensions manually
+ int numExtensions = eglHelper.glGetInteger(GLES30.GL_NUM_EXTENSIONS);
+ StringBuilder extensionsBuilder = new StringBuilder();
+ for (int i = 0; i < numExtensions; i++)
+ {
+ extensionsBuilder.append(eglHelper.glGetStringi(GLES30.GL_EXTENSIONS, i)).append('\n');
+ }
+ Input.add(new AboutFragmentItem("OpenGL ES 3.0 Extensions", extensionsBuilder.toString()));
+
+ AboutInfoFragmentAdapter adapter = new AboutInfoFragmentAdapter(getActivity(), R.layout.about_layout, Input);
+ rootView.setAdapter(adapter);
+
+ return rootView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLInfoFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLInfoFragment.java
new file mode 100644
index 0000000000..828b8dce92
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/GLInfoFragment.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import android.app.ListFragment;
+import android.opengl.GLES20;
+import android.opengl.GLES30;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.about.Limit.Type;
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * {@link ListFragment} responsible for displaying
+ * information relating to OpenGL.
+ */
+public final class GLInfoFragment extends ListFragment
+{
+ private final Limit[] Limits = {
+ new Limit("Vendor", GL10.GL_VENDOR, Type.STRING),
+ new Limit("Version", GL10.GL_VERSION, Type.STRING),
+ new Limit("Renderer", GL10.GL_RENDERER, Type.STRING),
+ new Limit("GLSL version", GLES20.GL_SHADING_LANGUAGE_VERSION, Type.STRING),
+ };
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ final EGLHelper eglHelper = new EGLHelper(EGLHelper.EGL_OPENGL_BIT);
+
+ ListView rootView = (ListView) inflater.inflate(R.layout.gamelist_listview, container, false);
+ List<AboutFragmentItem> Input = new ArrayList<AboutFragmentItem>();
+
+ for (Limit limit : Limits)
+ {
+ Log.i("GLInfoFragment", "Getting enum " + limit.name);
+ Input.add(new AboutFragmentItem(limit.name, limit.GetValue(eglHelper)));
+ }
+
+ // Get extensions manually
+ int numExtensions = eglHelper.glGetInteger(GLES30.GL_NUM_EXTENSIONS);
+ StringBuilder extensionsBuilder = new StringBuilder();
+ for (int i = 0; i < numExtensions; i++)
+ {
+ extensionsBuilder.append(eglHelper.glGetStringi(GL10.GL_EXTENSIONS, i)).append('\n');
+ }
+ Input.add(new AboutFragmentItem("OpenGL Extensions", extensionsBuilder.toString()));
+
+ AboutInfoFragmentAdapter adapter = new AboutInfoFragmentAdapter(getActivity(), R.layout.about_layout, Input);
+ rootView.setAdapter(adapter);
+
+ return rootView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/Limit.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/Limit.java
new file mode 100644
index 0000000000..9cbd15f62d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/about/Limit.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2014 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.about;
+
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+final class Limit
+{
+ /**
+ * Possible types a Limit can be.
+ */
+ enum Type
+ {
+ /** Generic string */
+ STRING,
+ /** Integer constant */
+ INTEGER,
+ /** Range of integers */
+ INTEGER_RANGE,
+ }
+
+ /** Name of this limit */
+ public final String name;
+ /** The GL constant that represents this limit.*/
+ public final int glEnum;
+ /** The {@link Type} of this limit. */
+ public final Type type;
+
+ /**
+ * Constructor
+ *
+ * @param name Name of the limit.
+ * @param glEnum The GL constant that represents this limit.
+ * @param type The {@link Type} of this limit.
+ */
+ public Limit(String name, int glEnum, Type type)
+ {
+ this.name = name;
+ this.glEnum = glEnum;
+ this.type = type;
+ }
+
+ /**
+ * Retrieves the information represented by this limit.
+ *
+ * @param context {@link EGLHelper} context to retrieve the limit with.
+ *
+ * @return the information represented by this limit.
+ */
+ public String GetValue(EGLHelper context)
+ {
+ if (type == Type.INTEGER)
+ {
+ return Integer.toString(context.glGetInteger(glEnum));
+ }
+ else if (type == Type.INTEGER_RANGE)
+ {
+ int[] data = new int[2];
+ context.getGL().glGetIntegerv(glEnum, data, 0);
+
+ return String.format("[%d, %d]", data[0], data[1]);
+ }
+
+ // If neither of the above type, assume it's a string.
+ return context.glGetString(glEnum);
+ }
+} \ No newline at end of file
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/EmulationActivity.java
new file mode 100644
index 0000000000..7775e2124c
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/EmulationActivity.java
@@ -0,0 +1,316 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.emulation;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.settings.input.InputConfigFragment;
+
+import java.util.List;
+
+/**
+ * This is the activity where all of the emulation handling happens.
+ * This activity is responsible for displaying the SurfaceView that we render to.
+ */
+public final class EmulationActivity extends Activity
+{
+ private boolean Running;
+ private boolean IsActionBarHidden = false;
+ private SharedPreferences sharedPrefs;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // Request window features for the emulation view.
+ getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
+ getWindow().addFlags(LayoutParams.FLAG_FULLSCREEN);
+ getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+
+ // Set the transparency for the action bar.
+ ColorDrawable actionBarBackground = new ColorDrawable(Color.parseColor("#303030"));
+ actionBarBackground.setAlpha(175);
+ getActionBar().setBackgroundDrawable(actionBarBackground);
+
+ // Get the intent passed from the GameList when the game
+ // was selected. This is so the path of the game can be retrieved
+ // and set on the native side of the code so the emulator can actually
+ // load the game.
+ Intent gameToEmulate = getIntent();
+ NativeLibrary.SetFilename(gameToEmulate.getStringExtra("SelectedGame"));
+ Running = true;
+
+ // Set the emulation window.
+ setContentView(R.layout.emulation_view);
+
+ // If the input overlay was previously disabled, then don't show it.
+ if (!sharedPrefs.getBoolean("showInputOverlay", true))
+ {
+ findViewById(R.id.emulationControlOverlay).setVisibility(View.INVISIBLE);
+ }
+
+ // Hide the action bar by default so it doesn't get in the way.
+ getActionBar().hide();
+ IsActionBarHidden = true;
+ }
+
+ @Override
+ public void onStop()
+ {
+ super.onStop();
+
+ if (Running)
+ NativeLibrary.StopEmulation();
+ }
+
+ @Override
+ public void onPause()
+ {
+ super.onPause();
+
+ if (Running)
+ NativeLibrary.PauseEmulation();
+ }
+
+ @Override
+ public void onResume()
+ {
+ super.onResume();
+
+ if (Running)
+ NativeLibrary.UnPauseEmulation();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ super.onDestroy();
+
+ if (Running)
+ {
+ NativeLibrary.StopEmulation();
+ Running = false;
+ }
+ }
+
+ @Override
+ public void onBackPressed()
+ {
+ // The back button in the emulation
+ // window is the toggle for the action bar.
+ if (IsActionBarHidden)
+ {
+ IsActionBarHidden = false;
+ getActionBar().show();
+ }
+ else
+ {
+ IsActionBarHidden = true;
+ getActionBar().hide();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.emuwindow_overlay, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu)
+ {
+ // Determine which string the "Enable Input Overlay" menu item should have
+ // depending on its visibility at the time of preparing the options menu.
+ if (!sharedPrefs.getBoolean("showInputOverlay", true))
+ {
+ menu.findItem(R.id.enableInputOverlay).setTitle(R.string.enable_input_overlay);
+ }
+ else
+ {
+ menu.findItem(R.id.enableInputOverlay).setTitle(R.string.disable_input_overlay);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int itemId, MenuItem item)
+ {
+ switch(item.getItemId())
+ {
+ // Enable/Disable input overlay.
+ case R.id.enableInputOverlay:
+ {
+ View overlay = findViewById(R.id.emulationControlOverlay);
+
+ // Show the overlay
+ if (item.getTitle().equals(getString(R.string.enable_input_overlay)))
+ {
+ overlay.setVisibility(View.VISIBLE);
+ item.setTitle(R.string.disable_input_overlay);
+ sharedPrefs.edit().putBoolean("showInputOverlay", true).apply();
+ }
+ else // Hide the overlay
+ {
+ overlay.setVisibility(View.INVISIBLE);
+ item.setTitle(R.string.enable_input_overlay);
+ sharedPrefs.edit().putBoolean("showInputOverlay", false).apply();
+ }
+
+ return true;
+ }
+
+ // Screenshot capturing
+ case R.id.takeScreenshot:
+ NativeLibrary.SaveScreenShot();
+ return true;
+
+ // Save state slots
+ case R.id.saveSlot1:
+ NativeLibrary.SaveState(0);
+ return true;
+
+ case R.id.saveSlot2:
+ NativeLibrary.SaveState(1);
+ return true;
+
+ case R.id.saveSlot3:
+ NativeLibrary.SaveState(2);
+ return true;
+
+ case R.id.saveSlot4:
+ NativeLibrary.SaveState(3);
+ return true;
+
+ case R.id.saveSlot5:
+ NativeLibrary.SaveState(4);
+ return true;
+
+ // Load state slots
+ case R.id.loadSlot1:
+ NativeLibrary.LoadState(0);
+ return true;
+
+ case R.id.loadSlot2:
+ NativeLibrary.LoadState(1);
+ return true;
+
+ case R.id.loadSlot3:
+ NativeLibrary.LoadState(2);
+ return true;
+
+ case R.id.loadSlot4:
+ NativeLibrary.LoadState(3);
+ return true;
+
+ case R.id.loadSlot5:
+ NativeLibrary.LoadState(4);
+ return true;
+
+ case R.id.exitEmulation:
+ {
+ // Create a confirmation method for quitting the current emulation instance.
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.overlay_exit_emulation);
+ builder.setMessage(R.string.overlay_exit_emulation_confirm);
+ builder.setNegativeButton(R.string.no, null);
+ builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which)
+ {
+ onDestroy();
+ }
+ });
+ builder.show();
+ return true;
+ }
+
+ default:
+ return super.onMenuItemSelected(itemId, item);
+ }
+ }
+
+ // Gets button presses
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event)
+ {
+ int action = 0;
+
+ if (Running)
+ {
+ switch (event.getAction())
+ {
+ case KeyEvent.ACTION_DOWN:
+ // Handling the case where the back button is pressed.
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK)
+ {
+ onBackPressed();
+ return true;
+ }
+
+ // Normal key events.
+ action = NativeLibrary.ButtonState.PRESSED;
+ break;
+ case KeyEvent.ACTION_UP:
+ action = NativeLibrary.ButtonState.RELEASED;
+ break;
+ default:
+ return false;
+ }
+ InputDevice input = event.getDevice();
+ boolean handled = NativeLibrary.onGamePadEvent(InputConfigFragment.getInputDesc(input), event.getKeyCode(), action);
+ return handled;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event)
+ {
+ if (((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0) || !Running)
+ {
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ // Don't attempt to do anything if we are disconnecting a device.
+ if (event.getActionMasked() == MotionEvent.ACTION_CANCEL)
+ return true;
+
+ InputDevice input = event.getDevice();
+ List<InputDevice.MotionRange> motions = input.getMotionRanges();
+
+ for (InputDevice.MotionRange range : motions)
+ {
+ NativeLibrary.onGamePadMoveEvent(InputConfigFragment.getInputDesc(input), range.getAxis(), event.getAxisValue(range.getAxis()));
+ }
+
+ return true;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/NativeGLSurfaceView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/NativeGLSurfaceView.java
new file mode 100644
index 0000000000..90acf05e8e
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/NativeGLSurfaceView.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.emulation;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * The {@link SurfaceView} that rendering is performed on.
+ */
+public final class NativeGLSurfaceView extends SurfaceView
+{
+ private static Thread myRun;
+ private static boolean Running = false;
+ private static boolean Created = false;
+
+ /**
+ * Constructor.
+ *
+ * @param context The current {@link Context}.
+ * @param attribs An AttributeSet for retrieving data from XML files.
+ */
+ public NativeGLSurfaceView(Context context, AttributeSet attribs)
+ {
+ super(context, attribs);
+
+ if (!Created)
+ {
+ myRun = new Thread()
+ {
+ @Override
+ public void run() {
+
+ NativeLibrary.Run(getHolder().getSurface());
+ Created = false;
+ Running = false;
+ }
+ };
+
+ getHolder().addCallback(new SurfaceHolder.Callback()
+ {
+ public void surfaceCreated(SurfaceHolder holder)
+ {
+ // TODO Auto-generated method stub
+ if (!Running)
+ {
+ myRun.start();
+ Running = true;
+ }
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
+ {
+ // TODO Auto-generated method stub
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder)
+ {
+ // TODO Auto-generated method stub
+ }
+ });
+
+ Created = true;
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java
new file mode 100644
index 0000000000..2a4cb10383
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlay.java
@@ -0,0 +1,288 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.emulation.overlay;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.NativeLibrary.ButtonState;
+import org.dolphinemu.dolphinemu.NativeLibrary.ButtonType;
+import org.dolphinemu.dolphinemu.R;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Draws the interactive input overlay on top of the
+ * {@link NativeGLSurfaceView} that is rendering emulation.
+ */
+public final class InputOverlay extends SurfaceView implements OnTouchListener
+{
+ private final Set<InputOverlayDrawableButton> overlayButtons = new HashSet<InputOverlayDrawableButton>();
+ private final Set<InputOverlayDrawableJoystick> overlayJoysticks = new HashSet<InputOverlayDrawableJoystick>();
+
+ /**
+ * Resizes a {@link Bitmap} by a given scale factor
+ *
+ * @param context The current {@link Context}
+ * @param bitmap The {@link Bitmap} to scale.
+ * @param scale The scale factor for the bitmap.
+ *
+ * @return The scaled {@link Bitmap}
+ */
+ public static Bitmap resizeBitmap(Context context, Bitmap bitmap, float scale)
+ {
+ // Retrieve screen dimensions.
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+
+ Bitmap bitmapResized = Bitmap.createScaledBitmap(bitmap,
+ (int)(dm.heightPixels * scale),
+ (int)(dm.heightPixels * scale),
+ true);
+ return bitmapResized;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param context The current {@link Context}.
+ * @param attrs {@link AttributeSet} for parsing XML attributes.
+ */
+ public InputOverlay(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+
+ // Add all the overlay items to the HashSet.
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_a, ButtonType.BUTTON_A));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_b, ButtonType.BUTTON_B));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_x, ButtonType.BUTTON_X));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_y, ButtonType.BUTTON_Y));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_z, ButtonType.BUTTON_Z));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_start, ButtonType.BUTTON_START));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_l, ButtonType.TRIGGER_L));
+ overlayButtons.add(initializeOverlayButton(context, R.drawable.gcpad_r, ButtonType.TRIGGER_R));
+ overlayJoysticks.add(initializeOverlayJoystick(context,
+ R.drawable.gcpad_joystick_range, R.drawable.gcpad_joystick,
+ ButtonType.STICK_MAIN));
+
+ // Set the on touch listener.
+ setOnTouchListener(this);
+
+ // Force draw
+ setWillNotDraw(false);
+
+ // Request focus for the overlay so it has priority on presses.
+ requestFocus();
+ }
+
+ @Override
+ public void draw(Canvas canvas)
+ {
+ super.draw(canvas);
+
+ for (InputOverlayDrawableButton button : overlayButtons)
+ {
+ button.draw(canvas);
+ }
+
+ for (InputOverlayDrawableJoystick joystick: overlayJoysticks)
+ {
+ joystick.draw(canvas);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event)
+ {
+ int pointerIndex = event.getActionIndex();
+
+ for (InputOverlayDrawableButton button : overlayButtons)
+ {
+ // Determine the button state to apply based on the MotionEvent action flag.
+ switch(event.getAction() & MotionEvent.ACTION_MASK)
+ {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ // If a pointer enters the bounds of a button, press that button.
+ if (button.getBounds().contains((int)event.getX(pointerIndex), (int)event.getY(pointerIndex)))
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.getId(), ButtonState.PRESSED);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ // If a pointer ends, release the button it was pressing.
+ if (button.getBounds().contains((int)event.getX(pointerIndex), (int)event.getY(pointerIndex)))
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.getId(), ButtonState.RELEASED);
+ break;
+ }
+ }
+
+
+ for (InputOverlayDrawableJoystick joystick : overlayJoysticks)
+ {
+ joystick.TrackEvent(event);
+ int[] axisIDs = joystick.getAxisIDs();
+ float[] axises = joystick.getAxisValues();
+
+ for (int i = 0; i < 4; i++)
+ NativeLibrary.onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, axisIDs[i], axises[i]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Initializes an InputOverlayDrawableButton, given by resId, with all of the
+ * parameters set for it to be properly shown on the InputOverlay.
+ * <p>
+ * This works due to the way the X and Y coordinates are stored within
+ * the {@link SharedPreferences}.
+ * <p>
+ * In the input overlay configuration menu,
+ * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
+ * the X and Y coordinates of the button at the END of its touch event
+ * (when you remove your finger/stylus from the touchscreen) are then stored
+ * within a SharedPreferences instance so that those values can be retrieved here.
+ * <p>
+ * This has a few benefits over the conventional way of storing the values
+ * (ie. within the Dolphin ini file).
+ * <ul>
+ * <li>No native calls</li>
+ * <li>Keeps Android-only values inside the Android environment</li>
+ * </ul>
+ * <p>
+ * Technically no modifications should need to be performed on the returned
+ * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
+ * for Android to call the onDraw method.
+ *
+ * @param context The current {@link Context}.
+ * @param resId The resource ID of the {@link Drawable} to get the {@link Bitmap} of.
+ * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
+ *
+ * @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set.
+ *
+ */
+ private static InputOverlayDrawableButton initializeOverlayButton(Context context, int resId, int buttonId)
+ {
+ // Resources handle for fetching the initial Drawable resource.
+ final Resources res = context.getResources();
+
+ // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Decide scale based on button ID
+ float scale;
+ float overlaySize = sPrefs.getInt("controls_size", 25);
+ overlaySize += 25;
+ overlaySize /= 50;
+
+ switch (resId)
+ {
+ case R.drawable.gcpad_b:
+ scale = 0.13f * overlaySize;
+ break;
+ case R.drawable.gcpad_x:
+ case R.drawable.gcpad_y:
+ scale = 0.18f * overlaySize;
+ break;
+ case R.drawable.gcpad_start:
+ scale = 0.12f * overlaySize;
+ break;
+ default:
+ scale = 0.20f * overlaySize;
+ break;
+ }
+
+ // Initialize the InputOverlayDrawableButton.
+ final Bitmap bitmap = resizeBitmap(context, BitmapFactory.decodeResource(res, resId), scale);
+ final InputOverlayDrawableButton overlayDrawable = new InputOverlayDrawableButton(res, bitmap, buttonId);
+
+ // String ID of the Drawable. This is what is passed into SharedPreferences
+ // to check whether or not a value has been set.
+ final String drawableId = res.getResourceEntryName(resId);
+
+ // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
+ // These were set in the input overlay configuration menu.
+ int drawableX = (int) sPrefs.getFloat(drawableId+"-X", 0f);
+ int drawableY = (int) sPrefs.getFloat(drawableId+"-Y", 0f);
+
+ // Intrinsic width and height of the InputOverlayDrawableButton.
+ // For any who may not know, intrinsic width/height
+ // are the original unmodified width and height of the image.
+ int intrinWidth = overlayDrawable.getIntrinsicWidth();
+ int intrinHeight = overlayDrawable.getIntrinsicHeight();
+
+ // Now set the bounds for the InputOverlayDrawableButton.
+ // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be.
+ overlayDrawable.setBounds(drawableX, drawableY, drawableX+intrinWidth, drawableY+intrinHeight);
+
+ return overlayDrawable;
+ }
+
+ /**
+ * Initializes an {@link InputOverlayDrawableJoystick}
+ *
+ * @param context The current {@link Context}
+ * @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
+ * @param resInner Resource ID for the inner image of the joystick (the one you actually move around).
+ * @param joystick Identifier for which joystick this is.
+ *
+ * @return the initialized {@link InputOverlayDrawableJoystick}.
+ */
+ private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context, int resOuter, int resInner, int joystick)
+ {
+ // Resources handle for fetching the initial Drawable resource.
+ final Resources res = context.getResources();
+
+ // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Initialize the InputOverlayDrawableJoystick.
+ float overlaySize = sPrefs.getInt("controls_size", 20);
+ overlaySize += 30;
+ overlaySize /= 50;
+ final Bitmap bitmapOuter = resizeBitmap(context, BitmapFactory.decodeResource(res, resOuter), 0.30f * overlaySize);
+ final Bitmap bitmapInner = BitmapFactory.decodeResource(res, resInner);
+
+ // String ID of the Drawable. This is what is passed into SharedPreferences
+ // to check whether or not a value has been set.
+ final String drawableId = res.getResourceEntryName(resOuter);
+
+ // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
+ // These were set in the input overlay configuration menu.
+ int drawableX = (int) sPrefs.getFloat(drawableId+"-X", 0f);
+ int drawableY = (int) sPrefs.getFloat(drawableId+"-Y", 0f);
+
+ // Now set the bounds for the InputOverlayDrawableJoystick.
+ // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be.
+ int outerSize = bitmapOuter.getWidth();
+ Rect outerRect = new Rect(drawableX, drawableY, drawableX + outerSize, drawableY + outerSize);
+ Rect innerRect = new Rect(0, 0, outerSize / 4, outerSize / 4);
+
+ final InputOverlayDrawableJoystick overlayDrawable
+ = new InputOverlayDrawableJoystick(res,
+ bitmapOuter, bitmapInner,
+ outerRect, innerRect,
+ joystick);
+
+
+ return overlayDrawable;
+ }
+
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java
new file mode 100644
index 0000000000..4c63f9541b
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableButton.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.emulation.overlay;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+
+/**
+ * Custom {@link BitmapDrawable} that is capable
+ * of storing it's own ID.
+ */
+public final class InputOverlayDrawableButton extends BitmapDrawable
+{
+ // The ID identifying what type of button this Drawable represents.
+ private int buttonType;
+
+ /**
+ * Constructor
+ *
+ * @param res {@link Resources} instance.
+ * @param bitmap {@link Bitmap} to use with this Drawable.
+ * @param buttonType Identifier for this type of button.
+ */
+ public InputOverlayDrawableButton(Resources res, Bitmap bitmap, int buttonType)
+ {
+ super(res, bitmap);
+
+ this.buttonType = buttonType;
+ }
+
+ /**
+ * Gets this InputOverlayDrawableButton's button ID.
+ *
+ * @return this InputOverlayDrawableButton's button ID.
+ */
+ public int getId()
+ {
+ return buttonType;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java
new file mode 100644
index 0000000000..a74211eabe
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/emulation/overlay/InputOverlayDrawableJoystick.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.emulation.overlay;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.MotionEvent;
+
+/**
+ * Custom {@link BitmapDrawable} that is capable
+ * of storing it's own ID.
+ */
+public final class InputOverlayDrawableJoystick extends BitmapDrawable
+{
+ private final int[] axisIDs = {0, 0, 0, 0};
+ private final float[] axises = {0f, 0f};
+ private final BitmapDrawable ringInner;
+ private int trackId = -1;
+
+ /**
+ * Constructor
+ *
+ * @param res {@link Resources} instance.
+ * @param bitmapOuter {@link Bitmap} which represents the outer non-movable part of the joystick.
+ * @param bitmapInner {@link Bitmap} which represents the inner movable part of the joystick.
+ * @param rectOuter {@link Rect} which represents the outer joystick bounds.
+ * @param rectInner {@link Rect} which represents the inner joystick bounds.
+ * @param joystick Identifier for which joystick this is.
+ */
+ public InputOverlayDrawableJoystick(Resources res,
+ Bitmap bitmapOuter, Bitmap bitmapInner,
+ Rect rectOuter, Rect rectInner,
+ int joystick)
+ {
+ super(res, bitmapOuter);
+ this.setBounds(rectOuter);
+
+ this.ringInner = new BitmapDrawable(res, bitmapInner);
+ this.ringInner.setBounds(rectInner);
+ SetInnerBounds();
+ this.axisIDs[0] = joystick + 1;
+ this.axisIDs[1] = joystick + 2;
+ this.axisIDs[2] = joystick + 3;
+ this.axisIDs[3] = joystick + 4;
+ }
+
+ @Override
+ public void draw(Canvas canvas)
+ {
+ super.draw(canvas);
+
+ ringInner.draw(canvas);
+ }
+
+ public void TrackEvent(MotionEvent event)
+ {
+ int pointerIndex = event.getActionIndex();
+
+ switch(event.getAction() & MotionEvent.ACTION_MASK)
+ {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (getBounds().contains((int)event.getX(pointerIndex), (int)event.getY(pointerIndex)))
+ trackId = event.getPointerId(pointerIndex);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (trackId == event.getPointerId(pointerIndex))
+ {
+ axises[0] = axises[1] = 0.0f;
+ SetInnerBounds();
+ trackId = -1;
+ }
+ break;
+ }
+
+ if (trackId == -1)
+ return;
+
+ for (int i = 0; i < event.getPointerCount(); i++)
+ {
+ if (trackId == event.getPointerId(i))
+ {
+ float touchX = event.getX(i);
+ float touchY = event.getY(i);
+ float maxY = getBounds().bottom;
+ float maxX = getBounds().right;
+ touchX -= getBounds().centerX();
+ maxX -= getBounds().centerX();
+ touchY -= getBounds().centerY();
+ maxY -= getBounds().centerY();
+ final float AxisX = touchX / maxX;
+ final float AxisY = touchY / maxY;
+ axises[0] = AxisY;
+ axises[1] = AxisX;
+
+ SetInnerBounds();
+ }
+ }
+ }
+
+ public float[] getAxisValues()
+ {
+ float[] joyaxises = {0f, 0f, 0f, 0f};
+ joyaxises[1] = Math.min(axises[0], 1.0f);
+ joyaxises[0] = Math.min(axises[0], 0.0f);
+ joyaxises[3] = Math.min(axises[1], 1.0f);
+ joyaxises[2] = Math.min(axises[1], 0.0f);
+ return joyaxises;
+ }
+
+ public int[] getAxisIDs()
+ {
+ return axisIDs;
+ }
+
+ private void SetInnerBounds()
+ {
+ float floatX = this.getBounds().centerX();
+ float floatY = this.getBounds().centerY();
+ floatY += axises[0] * (this.getBounds().height() / 2);
+ floatX += axises[1] * (this.getBounds().width() / 2);
+ int X = (int)(floatX);
+ int Y = (int)(floatY);
+ int width = this.ringInner.getBounds().width() / 2;
+ int height = this.ringInner.getBounds().height() / 2;
+ this.ringInner.setBounds(X - width, Y - height,
+ X + width, Y + height);
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowser.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowser.java
new file mode 100644
index 0000000000..669ffe0094
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowser.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.folderbrowser;
+
+import android.app.ListFragment;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import java.io.File;
+import java.util.*;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.gamelist.GameListActivity;
+
+/**
+ * A basic folder browser {@link ListFragment} that allows
+ * the user to select ISOs/ROMs for playing within the
+ * emulator.
+ * <p>
+ * Any valid ISO/ROM selected in this will be added to
+ * the game list for easy browsing the next time the
+ * application is used.
+ * <p>
+ * Note that this file browser does not display files
+ * or directories that are hidden
+ */
+public final class FolderBrowser extends ListFragment
+{
+ private FolderBrowserAdapter adapter;
+ private static File currentDir = null;
+
+ // Populates the FolderView with the given currDir's contents.
+ private void Fill(File currDir)
+ {
+ // Clear the adapter of previous items.
+ adapter.clear();
+
+ // Set the activity title to the current directory the FolderBrowser is in.
+ getActivity().setTitle(String.format(getString(R.string.current_dir), currDir.getName()));
+
+ File[] dirs = currDir.listFiles();
+ List<FolderBrowserItem> dir = new ArrayList<FolderBrowserItem>();
+ List<FolderBrowserItem> fls = new ArrayList<FolderBrowserItem>();
+
+ // Supported extensions to filter by
+ Set<String> validExts = new HashSet<String>(Arrays.asList(".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".wad", ".wbfs"));
+
+ // If dirs is null, then we don't have access permissions to the selected folder.
+ if (dirs != null)
+ {
+ // Search for any directories or files within the current dir.
+ for(File entry : dirs)
+ {
+ try
+ {
+ String entryName = entry.getName();
+ boolean hasExtension = (entryName.lastIndexOf(".") != -1);
+
+ // Skip hidden folders/files.
+ if (!entry.isHidden())
+ {
+ if(entry.isDirectory())
+ {
+ dir.add(new FolderBrowserItem(entryName, entry.getAbsolutePath()));
+ }
+ else if (entry.isFile() && hasExtension)
+ {
+ if (validExts.contains(entryName.toLowerCase().substring(entryName.lastIndexOf('.'))))
+ {
+ fls.add(new FolderBrowserItem(entryName, String.format(getString(R.string.file_size), entry.length()), entry.getAbsolutePath()));
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.e("FolderBrowser", ex.toString());
+ }
+ }
+ }
+
+ Collections.sort(dir);
+ Collections.sort(fls);
+ dir.addAll(fls);
+
+ // Check for a parent directory to the one we're currently in.
+ if (!currDir.getPath().equalsIgnoreCase("/"))
+ dir.add(0, new FolderBrowserItem("..", getString(R.string.parent_directory), currDir.getParent()));
+
+ // Add the items to the adapter and notify the adapter users of its new contents.
+ adapter.addAll(dir);
+ adapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onListItemClick(ListView lv, View v, int position, long id)
+ {
+ FolderBrowserItem item = adapter.getItem(position);
+ if(item.isDirectory())
+ {
+ currentDir = new File(item.getPath());
+ Fill(currentDir);
+ }
+ else
+ {
+ FolderSelected();
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ if(currentDir == null)
+ currentDir = new File(Environment.getExternalStorageDirectory().getPath());
+
+ ListView rootView = (ListView) inflater.inflate(R.layout.folderbrowser_listview, container, false);
+ adapter = new FolderBrowserAdapter(getActivity(), R.layout.folderbrowser_list_item);
+ rootView.setAdapter(adapter);
+
+ Fill(currentDir);
+ return rootView;
+ }
+
+ private void FolderSelected()
+ {
+ String Directories = NativeLibrary.GetConfig("Dolphin.ini", "General", "ISOPaths", "0");
+ int intDirectories = Integer.parseInt(Directories);
+
+ // Check to see if a path set in the Dolphin config
+ // matches the one the user is trying to add. If it's
+ // already set, then don't add it to the list again.
+ boolean pathNotPresent = true;
+ for (int i = 0; i < intDirectories; i++)
+ {
+ String isoPath = NativeLibrary.GetConfig("Dolphin.ini", "General", "ISOPath" + i, "");
+
+ pathNotPresent = !isoPath.equals(currentDir.getPath());
+ }
+
+ // User doesn't have this path in the config, so add it.
+ if (pathNotPresent)
+ {
+ NativeLibrary.SetConfig("Dolphin.ini", "General", "ISOPaths", Integer.toString(intDirectories+1));
+ NativeLibrary.SetConfig("Dolphin.ini", "General", "ISOPath" + Integer.toString(intDirectories), currentDir.getPath());
+ }
+
+ ((GameListActivity)getActivity()).SwitchPage(0);
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserAdapter.java
new file mode 100644
index 0000000000..d248554919
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserAdapter.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.folderbrowser;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.dolphinemu.dolphinemu.R;
+
+/**
+ * The {@link ArrayAdapter} that backs the file browser.
+ * <p>
+ * This is responsible for correctly handling the display
+ * of the items for the {@link FolderBrowser} UI.
+ */
+public final class FolderBrowserAdapter extends ArrayAdapter<FolderBrowserItem>
+{
+ // ViewHolder which is used to hold onto
+ // items within a listview. This is done
+ // so that findViewById is not needed to
+ // be excessively called over and over.
+ private static final class ViewHolder
+ {
+ TextView title;
+ TextView subtitle;
+ ImageView icon;
+ }
+
+ private final Context context;
+ private final int id;
+ private ViewHolder viewHolder;
+
+ /**
+ * 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 FolderBrowserAdapter(Context context, int resourceId)
+ {
+ super(context, resourceId);
+
+ this.context = context;
+ this.id = resourceId;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ if (convertView == null)
+ {
+ LayoutInflater vi = LayoutInflater.from(context);
+ convertView = vi.inflate(id, parent, false);
+
+ // Initialize the ViewHolder and store it.
+ viewHolder = new ViewHolder();
+ viewHolder.title = (TextView) convertView.findViewById(R.id.BrowserItemTitle);
+ viewHolder.subtitle = (TextView) convertView.findViewById(R.id.BrowserItemSubTitle);
+ viewHolder.icon = (ImageView) convertView.findViewById(R.id.BrowserItemIcon);
+ convertView.setTag(viewHolder);
+ }
+ else // Can recover the holder.
+ {
+ viewHolder = (ViewHolder) convertView.getTag();
+ }
+
+ final FolderBrowserItem item = getItem(position);
+ if (item != null)
+ {
+ if (viewHolder.title != null)
+ {
+ viewHolder.title.setText(item.getName());
+ }
+
+ if (viewHolder.subtitle != null)
+ {
+ // Remove the subtitle for all folders, except for the parent directory folder.
+ if (item.isDirectory() && !item.getSubtitle().equals(context.getString(R.string.parent_directory)))
+ {
+ viewHolder.subtitle.setVisibility(View.GONE);
+ }
+ else
+ {
+ viewHolder.subtitle.setVisibility(View.VISIBLE);
+ viewHolder.subtitle.setText(item.getSubtitle());
+ }
+ }
+
+ if (viewHolder.icon != null)
+ {
+ if (item.isDirectory())
+ {
+ viewHolder.icon.setImageResource(R.drawable.ic_menu_folder);
+ }
+ else
+ {
+ viewHolder.icon.setImageResource(R.drawable.ic_menu_file);
+ }
+ }
+ }
+ return convertView;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserItem.java
new file mode 100644
index 0000000000..e419162ed3
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/folderbrowser/FolderBrowserItem.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.folderbrowser;
+
+import java.io.File;
+
+/**
+ * Represents an item in the {@link FolderBrowser} list.
+ */
+public final class FolderBrowserItem implements Comparable<FolderBrowserItem>
+{
+ private final String name;
+ private final String subtitle;
+ private final String path;
+ private final File underlyingFile;
+
+ /**
+ * Constructor
+ *
+ * @param name The name of the file/folder represented by this item.
+ * @param subtitle The subtitle of this FolderBrowserItem.
+ * @param path The path of the file/folder represented by this item.
+ */
+ public FolderBrowserItem(String name, String subtitle, String path)
+ {
+ this.name = name;
+ this.subtitle = subtitle;
+ this.path = path;
+ this.underlyingFile = new File(path);
+ }
+
+ /**
+ * Constructor. Initializes a FolderBrowserItem with an empty subtitle.
+ *
+ * @param name The name of the file/folder represented by this item.
+ * @param path The path of the file/folder represented by this item.
+ */
+ public FolderBrowserItem(String name, String path)
+ {
+ this.name = name;
+ this.subtitle = "";
+ this.path = path;
+ this.underlyingFile = new File(path);
+ }
+
+ /**
+ * Gets the name of the file/folder represented by this FolderBrowserItem.
+ *
+ * @return the name of the file/folder represented by this FolderBrowserItem.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Gets the subtitle text of this FolderBrowserItem.
+ *
+ * @return the subtitle text of this FolderBrowserItem.
+ */
+ public String getSubtitle()
+ {
+ return subtitle;
+ }
+
+ /**
+ * Gets the path of the file/folder represented by this FolderBrowserItem.
+ *
+ * @return the path of the file/folder represented by this FolderBrowserItem.
+ */
+ public String getPath()
+ {
+ return path;
+ }
+
+ /**
+ * Gets the {@link File} representation of the underlying file/folder
+ * represented by this FolderBrowserItem.
+ *
+ * @return the {@link File} representation of the underlying file/folder
+ * represented by this FolderBrowserItem.
+ */
+ public File getUnderlyingFile()
+ {
+ return underlyingFile;
+ }
+
+ /**
+ * Gets whether or not this FolderBrowserItem represents a directory.
+ *
+ * @return true if this FolderBrowserItem represents a directory, false otherwise.
+ */
+ public boolean isDirectory()
+ {
+ return underlyingFile.isDirectory();
+ }
+
+ @Override
+ public int compareTo(FolderBrowserItem other)
+ {
+ if(name != null)
+ return name.toLowerCase().compareTo(other.getName().toLowerCase());
+ else
+ throw new NullPointerException("The name of this FolderBrowserItem is null");
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListActivity.java
new file mode 100644
index 0000000000..3accec11de
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListActivity.java
@@ -0,0 +1,374 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.gamelist;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.widget.DrawerLayout;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import org.dolphinemu.dolphinemu.AssetCopyService;
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.about.AboutActivity;
+import org.dolphinemu.dolphinemu.emulation.EmulationActivity;
+import org.dolphinemu.dolphinemu.folderbrowser.FolderBrowser;
+import org.dolphinemu.dolphinemu.settings.PrefsActivity;
+import org.dolphinemu.dolphinemu.sidemenu.SideMenuAdapter;
+import org.dolphinemu.dolphinemu.sidemenu.SideMenuItem;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The activity that implements all of the functions
+ * for the game list.
+ */
+public final class GameListActivity extends Activity
+ implements GameListFragment.OnGameListZeroListener
+{
+ private int mCurFragmentNum = 0;
+
+ private ActionBarDrawerToggle mDrawerToggle;
+ private DrawerLayout mDrawerLayout;
+ private SideMenuAdapter mDrawerAdapter;
+ private ListView mDrawerList;
+ private boolean mAutoStart = false;
+ private String mAutoStartFile = "";
+
+ /**
+ * Called from the {@link GameListFragment}.
+ * <p>
+ * This is called when there are no games
+ * currently present within the game list.
+ */
+ public void onZeroFiles()
+ {
+ mDrawerLayout.openDrawer(mDrawerList);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.gamelist_activity);
+
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mDrawerList = (ListView) findViewById(R.id.left_drawer);
+
+ // Construct list of items to add to the side menu.
+ List<SideMenuItem> dir = new ArrayList<SideMenuItem>();
+ dir.add(new SideMenuItem(getString(R.string.game_list), 0));
+ dir.add(new SideMenuItem(getString(R.string.browse_folder), 1));
+ dir.add(new SideMenuItem(getString(R.string.settings), 2));
+ dir.add(new SideMenuItem(getString(R.string.about), 3));
+
+ mDrawerAdapter = new SideMenuAdapter(this, R.layout.sidemenu, dir);
+ mDrawerList.setAdapter(mDrawerAdapter);
+ mDrawerList.setOnItemClickListener(mMenuItemClickListener);
+
+ // Enable ActionBar app icon to behave as action to toggle nav drawer
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setHomeButtonEnabled(true);
+
+ // ActionBarDrawerToggle ties together the the proper interactions
+ // between the sliding drawer and the action bar app icon
+ mDrawerToggle = new ActionBarDrawerToggle(
+ this, /* Host Activity */
+ mDrawerLayout, /* DrawerLayout object */
+ R.drawable.ic_drawer, /* Navigation drawer image to replace 'Up' caret */
+ R.string.drawer_open, /* "open drawer" description for accessibility */
+ R.string.drawer_close /* "close drawer" description for accessibility */
+ ) {
+ public void onDrawerClosed(View view) {
+ invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+ }
+
+ public void onDrawerOpened(View drawerView) {
+ invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
+ }
+ };
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+
+ CheckForIntent();
+
+ String BaseDir = NativeLibrary.GetUserDirectory();
+ final String DefaultDir = Environment.getExternalStorageDirectory() + File.separator + "dolphin-emu";
+ if (BaseDir.isEmpty())
+ BaseDir = DefaultDir;
+ NativeLibrary.SetUserDirectory(BaseDir);
+
+ // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
+ if (savedInstanceState == null)
+ {
+ // Copy assets into appropriate locations.
+ Intent copyAssets = new Intent(this, AssetCopyService.class);
+ startService(copyAssets);
+
+ // Display the game list fragment.
+ final GameListFragment gameList = new GameListFragment();
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(R.id.content_frame, gameList);
+ ft.commit();
+ }
+
+ // Create an alert telling them that their phone sucks
+ if (Build.CPU_ABI.contains("arm") && !NativeLibrary.SupportsNEON())
+ {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.device_compat_warning);
+ builder.setMessage(R.string.device_compat_warning_msg);
+ builder.setPositiveButton(R.string.yes, null);
+ builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which)
+ {
+ finish();
+ }
+ });
+ builder.show();
+ }
+
+ if (mAutoStart)
+ {
+ // Start the emulation activity
+ Intent intent = new Intent(this, EmulationActivity.class);
+ intent.putExtra("SelectedGame", mAutoStartFile);
+ startActivity(intent);
+ }
+ }
+
+ private void CheckForIntent()
+ {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean handled = prefs.getBoolean("HandledIntent", false);
+
+ // Get an editor.
+ SharedPreferences.Editor editor = prefs.edit();
+
+ Bundle extras = getIntent().getExtras();
+
+ if (!handled && extras != null)
+ {
+ // Application got passed extra data
+ editor.putBoolean("HandledIntent", true);
+ editor.apply();
+
+ // Did we get passed a new user directory?
+ String user_dir = extras.getString("UserDir");
+ if (user_dir != null && user_dir.length() != 0)
+ NativeLibrary.SetUserDirectory(user_dir);
+
+ // Did we get passed a file?
+ String start_file = extras.getString("AutoStartFile");
+ if (start_file != null && start_file.length() != 0)
+ {
+ mAutoStart = true;
+ mAutoStartFile = start_file;
+ }
+ return;
+ }
+
+ editor.putBoolean("HandledIntent", false);
+ editor.apply();
+ }
+
+ /**
+ * Switches to the {@link Fragment} represented
+ * by the given ID number.
+ *
+ * @param toPage the number representing the {@link Fragment} to switch to.
+ */
+ public void SwitchPage(int toPage)
+ {
+ if (mCurFragmentNum == toPage)
+ return;
+
+ switch(toPage)
+ {
+ case 0: // Game list
+ {
+ // We use the title section as the browser directory tracker in the folder browser.
+ // Make sure we flip the title back if we're coming from that fragment.
+ if (mCurFragmentNum == 1)
+ setTitle(R.string.app_name);
+
+ mCurFragmentNum = 0;
+ final GameListFragment gameList = new GameListFragment();
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(R.id.content_frame, gameList);
+ ft.commit();
+ invalidateOptionsMenu();
+ }
+ break;
+
+ case 1: // Folder Browser
+ {
+ mCurFragmentNum = 1;
+ final FolderBrowser folderBrowser = new FolderBrowser();
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.replace(R.id.content_frame, folderBrowser);
+ ft.addToBackStack(null);
+ ft.commit();
+ invalidateOptionsMenu();
+ }
+ break;
+
+ case 2: // Settings
+ {
+ Intent intent = new Intent(this, PrefsActivity.class);
+ startActivity(intent);
+ }
+ break;
+
+ case 3: // About
+ {
+ Intent intent = new Intent(this, AboutActivity.class);
+ startActivity(intent);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ private final AdapterView.OnItemClickListener mMenuItemClickListener = new AdapterView.OnItemClickListener()
+ {
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id)
+ {
+ SideMenuItem o = mDrawerAdapter.getItem(position);
+ mDrawerLayout.closeDrawer(mDrawerList);
+ SwitchPage(o.getID());
+ }
+ };
+
+ /**
+ * When using the ActionBarDrawerToggle, you must call it during
+ * onPostCreate() and onConfigurationChanged()...
+ */
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState)
+ {
+ super.onPostCreate(savedInstanceState);
+
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ mDrawerToggle.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig)
+ {
+ super.onConfigurationChanged(newConfig);
+
+ // Pass any configuration change to the drawer toggle
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
+ // Only show this in the game list.
+ if (mCurFragmentNum == 0)
+ {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.gamelist_menu, menu);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ // The action bar home/up action should open or close the drawer.
+ // ActionBarDrawerToggle will take care of this.
+ if (mDrawerToggle.onOptionsItemSelected(item))
+ {
+ return true;
+ }
+
+ // If clear game list is pressed.
+ if (item.getItemId() == R.id.clearGameList)
+ {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.clear_game_list);
+ builder.setMessage(getString(R.string.clear_game_list_confirm));
+ builder.setNegativeButton(R.string.no, null);
+ builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which)
+ {
+ String directories = NativeLibrary.GetConfig("Dolphin.ini", "General", "ISOPaths", "0");
+ int intDirs = Integer.parseInt(directories);
+
+ for (int i = 0; i < intDirs; i++)
+ {
+ NativeLibrary.SetConfig("Dolphin.ini", "General", "ISOPath" + i, "");
+ }
+
+ // Since we flushed all paths, we signify this in the ini.
+ NativeLibrary.SetConfig("Dolphin.ini", "General", "ISOPaths", "0");
+
+ // Now finally, clear the game list.
+ ((GameListFragment) getFragmentManager().findFragmentById(R.id.content_frame)).clearGameList();
+ }
+ });
+
+ builder.show();
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState)
+ {
+ super.onSaveInstanceState(outState);
+
+ outState.putInt("currentFragmentNum", mCurFragmentNum);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState)
+ {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ mCurFragmentNum = savedInstanceState.getInt("currentFragmentNum");
+ }
+
+ @Override
+ public void onBackPressed()
+ {
+ if (mCurFragmentNum == 0)
+ {
+ finish();
+ }
+ else
+ {
+ SwitchPage(0);
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListAdapter.java
new file mode 100644
index 0000000000..4ca43f6250
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListAdapter.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.gamelist;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.dolphinemu.dolphinemu.R;
+
+/**
+ * The adapter backing the game list.
+ * <p>
+ * Responsible for handling each game list item individually.
+ */
+public final class GameListAdapter extends ArrayAdapter<GameListItem>
+{
+ private static final float BYTES_PER_GIB = 1024 * 1024 * 1024;
+ private static final float BYTES_PER_MIB = 1024 * 1024;
+
+ private final Context context;
+ private final int id;
+
+ /**
+ * 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 GameListAdapter(Context context, int resourceId)
+ {
+ super(context, resourceId);
+
+ this.context = context;
+ this.id = resourceId;
+ }
+
+ @Override
+ public View getView(int position, View gameView, ViewGroup parent)
+ {
+ ViewHolder holder;
+
+ // If getView() was called without passing in a recycled view,
+ if (gameView == null)
+ {
+ // Inflate a new view using the appropriate XML layout.
+ LayoutInflater inflater = LayoutInflater.from(context);
+ gameView = inflater.inflate(id, parent, false);
+
+ // Instantiate a holder to contain references to the game's views.
+ holder = new ViewHolder();
+ holder.title = (TextView) gameView.findViewById(R.id.GameListItemTitle);
+ holder.subtitle = (TextView) gameView.findViewById(R.id.GameListItemSubTitle);
+ holder.icon = (ImageView) gameView.findViewById(R.id.GameListItemIcon);
+
+ // Attach this list of references to the view.
+ gameView.setTag(holder);
+ }
+ // If we do have a recycled view, we can use the references it already contains.
+ else
+ {
+ holder = (ViewHolder) gameView.getTag();
+ }
+
+ // Get a reference to the game represented by this row.
+ final GameListItem item = getItem(position);
+
+ // Whether this row's view is newly created or not, set the children to contain the game's data.
+ if (item != null)
+ {
+ holder.title.setText(item.getName());
+
+ Bitmap icon = item.getImage();
+
+ if (icon != null)
+ {
+ holder.icon.setImageBitmap(icon);
+ }
+ else
+ {
+ holder.icon.setImageResource(R.drawable.no_banner);
+ }
+
+ float fileSize = item.getFilesize() / BYTES_PER_GIB;
+
+ String subtitle;
+
+ if (fileSize >= 1.0f)
+ {
+ subtitle = String.format(context.getString(R.string.file_size_gib), fileSize);
+ }
+ else
+ {
+ fileSize = item.getFilesize() / BYTES_PER_MIB;
+ subtitle = String.format(context.getString(R.string.file_size_mib), fileSize);
+ }
+
+ holder.subtitle.setText(subtitle);
+ }
+
+ // Make every other game in the list grey
+ if (position % 2 == 1)
+ gameView.setBackgroundColor(0xFFE3E3E3);
+
+ return gameView;
+ }
+
+ private final class ViewHolder
+ {
+ public TextView title;
+ public TextView subtitle;
+ public ImageView icon;
+ }
+}
+
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListFragment.java
new file mode 100644
index 0000000000..7dd5d0e14d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListFragment.java
@@ -0,0 +1,150 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.gamelist;
+
+import android.app.Activity;
+import android.app.ListFragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.emulation.EmulationActivity;
+
+
+/**
+ * The {@link ListFragment} responsible for displaying the game list.
+ */
+public final class GameListFragment extends ListFragment
+{
+ private GameListAdapter mGameAdapter;
+ private OnGameListZeroListener mCallback;
+
+ /**
+ * Interface that defines how to handle the case
+ * when there are zero games in the game list.
+ */
+ public interface OnGameListZeroListener
+ {
+ /**
+ * This is called when there are no games
+ * currently present within the game list.
+ */
+ void onZeroFiles();
+ }
+
+ /**
+ * Clears all entries from the {@link GameListAdapter}
+ * backing this GameListFragment.
+ */
+ public void clearGameList()
+ {
+ mGameAdapter.clear();
+ mGameAdapter.notifyDataSetChanged();
+ }
+
+ private void fill()
+ {
+ List<GameListItem> fls = new ArrayList<GameListItem>();
+ String Directories = NativeLibrary.GetConfig("Dolphin.ini", "General", "ISOPaths", "0");
+ int intDirectories = Integer.parseInt(Directories);
+
+ // Extensions to filter by.
+ Set<String> exts = new HashSet<String>(Arrays.asList(".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".wad", ".wbfs"));
+
+ for (int a = 0; a < intDirectories; ++a)
+ {
+ String BrowseDir = NativeLibrary.GetConfig("Dolphin.ini", "General", "ISOPath" + a, "");
+ File currentDir = new File(BrowseDir);
+ File[] dirs = currentDir.listFiles();
+ try
+ {
+ for (File entry : dirs)
+ {
+ String entryName = entry.getName();
+
+ if (!entry.isHidden() && !entry.isDirectory())
+ {
+ if (exts.contains(entryName.toLowerCase().substring(entryName.lastIndexOf('.'))))
+ fls.add(new GameListItem(getActivity(), entryName, entry.length(), entry.getAbsolutePath()));
+ }
+
+ }
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ Collections.sort(fls);
+
+ // Add all the items to the adapter
+ mGameAdapter.addAll(fls);
+ mGameAdapter.notifyDataSetChanged();
+
+ if (fls.isEmpty())
+ {
+ mCallback.onZeroFiles();
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
+ {
+ ListView rootView = (ListView) inflater.inflate(R.layout.gamelist_listview, container, false);
+ mGameAdapter = new GameListAdapter(getActivity(), R.layout.gamelist_list_item);
+ rootView.setAdapter(mGameAdapter);
+
+ fill();
+
+ return rootView;
+ }
+
+ @Override
+ public void onListItemClick(ListView listView, View view, int position, long id)
+ {
+ GameListItem item = mGameAdapter.getItem(position);
+
+ // Show a toast indicating which game was clicked.
+ Toast.makeText(getActivity(), String.format(getString(R.string.file_clicked), item.getPath()), Toast.LENGTH_SHORT).show();
+
+ // Start the emulation activity and send the path of the clicked ROM to it.
+ Intent intent = new Intent(getActivity(), EmulationActivity.class);
+ intent.putExtra("SelectedGame", item.getPath());
+ startActivity(intent);
+ }
+
+ @Override
+ public void onAttach(Activity activity)
+ {
+ super.onAttach(activity);
+
+ // This makes sure that the container activity has implemented
+ // the callback interface. If not, it throws an exception
+ try
+ {
+ mCallback = (OnGameListZeroListener) activity;
+ }
+ catch (ClassCastException e)
+ {
+ throw new ClassCastException(activity.toString()
+ + " must implement OnGameListZeroListener");
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListItem.java
new file mode 100644
index 0000000000..6e25a33804
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/gamelist/GameListItem.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.gamelist;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import java.io.File;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+
+/**
+ * Represents an item in the game list.
+ */
+public final class GameListItem implements Comparable<GameListItem>
+{
+ private String name;
+ private long filesize;
+ private final String path;
+ private Bitmap image;
+
+ /**
+ * Constructor.
+ *
+ * @param ctx The current {@link Context}
+ * @param name The name of this GameListItem.
+ * @param filesize The filesize for this GameListItem, in GiB
+ * @param path The file path for the game represented by this GameListItem.
+ */
+ public GameListItem(Context ctx, String name, long filesize, String path)
+ {
+ this.name = name;
+ this.filesize = filesize;
+ this.path = path;
+
+ File file = new File(path);
+ if (!file.isDirectory() && !path.isEmpty())
+ {
+ int[] Banner = NativeLibrary.GetBanner(path);
+ if (Banner[0] != 0)
+ {
+ image = Bitmap.createBitmap(Banner, 96, 32, Bitmap.Config.ARGB_8888);
+ }
+
+ this.name = NativeLibrary.GetTitle(path);
+ }
+ }
+
+ /**
+ * Gets the name of this GameListItem.
+ *
+ * @return the name of this GameListItem.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Gets the filesize of this GameListItem, in GiB.
+ *
+ * @return the filesize of this GameListItem.
+ */
+ public long getFilesize()
+ {
+ return filesize;
+ }
+
+ /**
+ * Gets the file path of the game represented by this GameListItem.
+ *
+ * @return the file path of the game represented by this GameListItem.
+ */
+ public String getPath()
+ {
+ return path;
+ }
+
+ /**
+ * Gets the image data for this game as a {@link Bitmap}.
+ *
+ * @return the image data for this game as a {@link Bitmap}.
+ */
+ public Bitmap getImage()
+ {
+ return image;
+ }
+
+ @Override
+ public int compareTo(GameListItem o)
+ {
+ if (name != null)
+ return name.toLowerCase().compareTo(o.getName().toLowerCase());
+ else
+ throw new NullPointerException("The name of this GameListItem is null");
+ }
+}
+
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/PrefsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/PrefsActivity.java
new file mode 100644
index 0000000000..0ffac65db9
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/PrefsActivity.java
@@ -0,0 +1,101 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.settings.cpu.CPUSettingsFragment;
+import org.dolphinemu.dolphinemu.settings.input.InputConfigFragment;
+import org.dolphinemu.dolphinemu.settings.video.VideoSettingsFragment;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+
+/**
+ * Main activity that manages all of the preference fragments used to display
+ * the settings to the user.
+ */
+public final class PrefsActivity extends Activity implements OnSharedPreferenceChangeListener
+{
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.viewpager);
+
+ // Set the ViewPager adapter.
+ final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
+ viewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+
+ // Register the preference change listener.
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ sPrefs.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
+ {
+ // If any change is made to the preferences in the front-end, immediately save them.
+ UserPreferences.SavePrefsToIni(this);
+ }
+
+ /**
+ * A {@link FragmentPagerAdapter} that returns a fragment
+ * corresponding to one of the sections/tabs/pages.
+ */
+ private final class ViewPagerAdapter extends FragmentPagerAdapter
+ {
+ private final String[] pageTitles = {
+ getString(R.string.cpu_settings),
+ getString(R.string.input_settings),
+ getString(R.string.video_settings)
+ };
+
+ public ViewPagerAdapter(FragmentManager fm)
+ {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int position)
+ {
+ switch(position)
+ {
+ case 0:
+ return new CPUSettingsFragment();
+
+ case 1:
+ return new InputConfigFragment();
+
+ case 2:
+ return new VideoSettingsFragment();
+
+ default: // Should never happen.
+ return null;
+ }
+ }
+
+ @Override
+ public int getCount()
+ {
+ // Show total pages.
+ return 3;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position)
+ {
+ return pageTitles[position];
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/UserPreferences.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/UserPreferences.java
new file mode 100644
index 0000000000..da46daeccf
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/UserPreferences.java
@@ -0,0 +1,284 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.preference.PreferenceManager;
+
+/**
+ * A class that retrieves all of the set user preferences in Android, in a safe way.
+ * <p>
+ * If any preferences are added to this emulator, an accessor for that preference
+ * should be added here. This way lengthy calls to getters from SharedPreferences
+ * aren't made necessary.
+ */
+public final class UserPreferences
+{
+ private UserPreferences()
+ {
+ // Disallows instantiation.
+ }
+
+ /**
+ * Loads the settings stored in the Dolphin ini config files to the shared preferences of this front-end.
+ *
+ * @param ctx The context used to retrieve the SharedPreferences instance.
+ */
+ public static void LoadIniToPrefs(Context ctx)
+ {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
+
+ // Get an editor.
+ SharedPreferences.Editor editor = prefs.edit();
+
+ // Add the settings.
+ if (Build.CPU_ABI.contains("arm64"))
+ editor.putString("cpuCorePref", getConfig("Dolphin.ini", "Core", "CPUCore", "4"));
+ else
+ editor.putString("cpuCorePref", getConfig("Dolphin.ini", "Core", "CPUCore", "3"));
+
+ editor.putBoolean("dualCorePref", getConfig("Dolphin.ini", "Core", "CPUThread", "False").equals("True"));
+ editor.putBoolean("fastmemPref", getConfig("Dolphin.ini", "Core", "Fastmem", "False").equals("True"));
+
+ editor.putString("gpuPref", getConfig("Dolphin.ini", "Core", "GFXBackend", "OGL"));
+ editor.putBoolean("showFPS", getConfig("gfx_opengl.ini", "Settings", "ShowFPS", "False").equals("True"));
+ editor.putBoolean("drawOnscreenControls", getConfig("Dolphin.ini", "Android", "ScreenControls", "True").equals("True"));
+
+ editor.putString("internalResolution", getConfig("gfx_opengl.ini", "Settings", "EFBScale", "2") );
+ editor.putString("FSAA", getConfig("gfx_opengl.ini", "Settings", "MSAA", "0"));
+ editor.putString("anisotropicFiltering", getConfig("gfx_opengl.ini", "Enhancements", "MaxAnisotropy", "0"));
+ editor.putString("postProcessingShader", getConfig("gfx_opengl.ini", "Enhancements", "PostProcessingShader", ""));
+ editor.putBoolean("scaledEFBCopy", getConfig("gfx_opengl.ini", "Hacks", "EFBScaledCopy", "True").equals("True"));
+ editor.putBoolean("perPixelLighting", getConfig("gfx_opengl.ini", "Settings", "EnablePixelLighting", "False").equals("True"));
+ editor.putBoolean("forceTextureFiltering", getConfig("gfx_opengl.ini", "Enhancements", "ForceFiltering", "False").equals("True"));
+ editor.putBoolean("disableFog", getConfig("gfx_opengl.ini", "Settings", "DisableFog", "False").equals("True"));
+ editor.putBoolean("skipEFBAccess", getConfig("gfx_opengl.ini", "Hacks", "EFBAccessEnable", "False").equals("True"));
+ editor.putBoolean("ignoreFormatChanges", getConfig("gfx_opengl.ini", "Hacks", "EFBEmulateFormatChanges", "False").equals("True"));
+ editor.putString("stereoscopyMode", getConfig("gfx_opengl.ini", "Enhancements", "StereoMode", "0"));
+ editor.putBoolean("stereoSwapEyes", getConfig("gfx_opengl.ini", "Enhancements", "StereoSwapEyes", "False").equals("True"));
+ editor.putString("stereoDepth", getConfig("gfx_opengl.ini", "Enhancements", "StereoDepth", "20"));
+ editor.putString("stereoConvergence", getConfig("gfx_opengl.ini", "Enhancements", "StereoConvergence", "20"));
+
+ String efbCopyOn = getConfig("gfx_opengl.ini", "Hacks", "EFBCopyEnable", "True");
+ String efbToTexture = getConfig("gfx_opengl.ini", "Hacks", "EFBToTextureEnable", "True");
+ String efbCopyCache = getConfig("gfx_opengl.ini", "Hacks", "EFBCopyCacheEnable", "False");
+
+ if (efbCopyOn.equals("False"))
+ {
+ editor.putString("efbCopyMethod", "Off");
+ }
+ else if (efbCopyOn.equals("True") && efbToTexture.equals("True"))
+ {
+ editor.putString("efbCopyMethod", "Texture");
+ }
+ else if(efbCopyOn.equals("True") && efbToTexture.equals("False") && efbCopyCache.equals("False"))
+ {
+ editor.putString("efbCopyMethod", "RAM (uncached)");
+ }
+ else if(efbCopyOn.equals("True") && efbToTexture.equals("False") && efbCopyCache.equals("True"))
+ {
+ editor.putString("efbCopyMethod", "RAM (cached)");
+ }
+
+ editor.putString("textureCacheAccuracy", getConfig("gfx_opengl.ini", "Settings", "SafeTextureCacheColorSamples", "128"));
+
+ String usingXFB = getConfig("gfx_opengl.ini", "Settings", "UseXFB", "False");
+ String usingRealXFB = getConfig("gfx_opengl.ini", "Settings", "UseRealXFB", "False");
+
+ if (usingXFB.equals("False"))
+ {
+ editor.putString("externalFrameBuffer", "Disabled");
+ }
+ else if (usingXFB.equals("True") && usingRealXFB.equals("False"))
+ {
+ editor.putString("externalFrameBuffer", "Virtual");
+ }
+ else if (usingXFB.equals("True") && usingRealXFB.equals("True"))
+ {
+ editor.putString("externalFrameBuffer", "Real");
+ }
+
+ editor.putBoolean("disableDestinationAlpha", getConfig("gfx_opengl.ini", "Settings", "DstAlphaPass", "False").equals("True"));
+ editor.putBoolean("fastDepthCalculation", getConfig("gfx_opengl.ini", "Settings", "FastDepthCalc", "True").equals("True"));
+ editor.putString("aspectRatio", getConfig("gfx_opengl.ini", "Settings", "AspectRatio", "0"));
+
+ // Apply the changes.
+ editor.apply();
+ }
+
+ // Small utility method that shortens calls to NativeLibrary.GetConfig.
+ private static String getConfig(String ini, String section, String key, String defaultValue)
+ {
+ return NativeLibrary.GetConfig(ini, section, key, defaultValue);
+ }
+
+ /**
+ * Writes the preferences set in the front-end to the Dolphin ini files.
+ *
+ * @param ctx The context used to retrieve the user settings.
+ * */
+ public static void SavePrefsToIni(Context ctx)
+ {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
+
+ // Whether or not the user is using dual core.
+ boolean isUsingDualCore = prefs.getBoolean("dualCorePref", true);
+
+ // Current CPU core being used. Falls back to interpreter upon error.
+ String currentEmuCore = prefs.getString("cpuCorePref", "0");
+
+ // Fastmem JIT core usage
+ boolean isUsingFastmem = prefs.getBoolean("fastmemPref", false);
+
+ // Current video backend being used. Falls back to software rendering upon error.
+ String currentVideoBackend = prefs.getString("gpuPref", "Software Rendering");
+
+ // Whether or not FPS will be displayed on-screen.
+ boolean showingFPS = prefs.getBoolean("showFPS", false);
+
+ // Whether or not to draw on-screen controls.
+ boolean drawingOnscreenControls = prefs.getBoolean("drawOnscreenControls", true);
+
+ // Whether or not to ignore all EFB access requests from the CPU.
+ boolean skipEFBAccess = prefs.getBoolean("skipEFBAccess", false);
+
+ // Whether or not to ignore changes to the EFB format.
+ boolean ignoreFormatChanges = prefs.getBoolean("ignoreFormatChanges", false);
+
+ // EFB copy method to use.
+ String efbCopyMethod = prefs.getString("efbCopyMethod", "Texture");
+
+ // Texture cache accuracy. Falls back to "Fast" up error.
+ String textureCacheAccuracy = prefs.getString("textureCacheAccuracy", "128");
+
+ // External frame buffer emulation. Falls back to disabled upon error.
+ String externalFrameBuffer = prefs.getString("externalFrameBuffer", "Disabled");
+
+ // Whether or not to disable destination alpha.
+ boolean disableDstAlphaPass = prefs.getBoolean("disableDestinationAlpha", false);
+
+ // Whether or not to use fast depth calculation.
+ boolean useFastDepthCalc = prefs.getBoolean("fastDepthCalculation", true);
+
+ // Aspect ratio selection
+ String aspectRatio = prefs.getString("aspectRatio", "0");
+
+ // Internal resolution. Falls back to 1x Native upon error.
+ String internalResolution = prefs.getString("internalResolution", "2");
+
+ // FSAA Level. Falls back to 1x upon error.
+ String FSAALevel = prefs.getString("FSAA", "0");
+
+ // Anisotropic Filtering Level. Falls back to 1x upon error.
+ String anisotropicFiltLevel = prefs.getString("anisotropicFiltering", "0");
+
+ // Post processing shader setting
+ String postProcessing = prefs.getString("postProcessingShader", "");
+
+ // Whether or not Scaled EFB copies are used.
+ boolean usingScaledEFBCopy = prefs.getBoolean("scaledEFBCopy", true);
+
+ // Whether or not per-pixel lighting is used.
+ boolean usingPerPixelLighting = prefs.getBoolean("perPixelLighting", false);
+
+ // Whether or not texture filtering is being forced.
+ boolean isForcingTextureFiltering = prefs.getBoolean("forceTextureFiltering", false);
+
+ // Whether or not fog is disabled.
+ boolean fogIsDisabled = prefs.getBoolean("disableFog", false);
+
+ // Stereoscopy setting
+ String stereoscopyMode = prefs.getString("stereoscopyMode", "0");
+
+ // Stereoscopy swap eyes
+ boolean stereoscopyEyeSwap = prefs.getBoolean("stereoSwapEyes", false);
+
+ // Stereoscopy separation
+ String stereoscopySeparation = prefs.getString("stereoDepth", "20");
+
+ // Stereoscopy convergence
+ String stereoscopyConvergence = prefs.getString("stereoConvergence", "20");
+
+ // CPU related Settings
+ NativeLibrary.SetConfig("Dolphin.ini", "Core", "CPUCore", currentEmuCore);
+ NativeLibrary.SetConfig("Dolphin.ini", "Core", "CPUThread", isUsingDualCore ? "True" : "False");
+ NativeLibrary.SetConfig("Dolphin.ini", "Core", "Fastmem", isUsingFastmem ? "True" : "False");
+
+ // General Video Settings
+ NativeLibrary.SetConfig("Dolphin.ini", "Core", "GFXBackend", currentVideoBackend);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "ShowFPS", showingFPS ? "True" : "False");
+ NativeLibrary.SetConfig("Dolphin.ini", "Android", "ScreenControls", drawingOnscreenControls ? "True" : "False");
+
+ // Video Hack Settings
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBAccessEnable", skipEFBAccess ? "False" : "True");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBEmulateFormatChanges", ignoreFormatChanges ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "AspectRatio", aspectRatio);
+
+ // Set EFB Copy Method
+ if (efbCopyMethod.equals("Off"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBCopyEnable", "False");
+ }
+ else if (efbCopyMethod.equals("Texture"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBCopyEnable", "True");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBToTextureEnable", "True");
+ }
+ else if (efbCopyMethod.equals("RAM (uncached)"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBCopyEnable", "True");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBToTextureEnable", "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBCopyCacheEnable", "False");
+ }
+ else if (efbCopyMethod.equals("RAM (cached)"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBCopyEnable", "True");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBToTextureEnable", "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBCopyCacheEnable", "True");
+ }
+
+ // Set texture cache accuracy
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "SafeTextureCacheColorSamples", textureCacheAccuracy);
+
+ // Set external frame buffer.
+ if (externalFrameBuffer.equals("Disabled"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "UseXFB", "False");
+ }
+ else if (externalFrameBuffer.equals("Virtual"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "UseXFB", "True");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "UseRealXFB", "False");
+ }
+ else if (externalFrameBuffer.equals("Real"))
+ {
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "UseXFB", "True");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "UseRealXFB", "True");
+ }
+
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "DstAlphaPass", disableDstAlphaPass ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "FastDepthCalc", useFastDepthCalc ? "True" : "False");
+
+ //-- Enhancement Settings --//
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "EFBScale", internalResolution);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "MSAA", FSAALevel);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "MaxAnisotropy", anisotropicFiltLevel);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "PostProcessingShader", postProcessing);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Hacks", "EFBScaledCopy", usingScaledEFBCopy ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "EnablePixelLighting", usingPerPixelLighting ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "ForceFiltering", isForcingTextureFiltering ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Settings", "DisableFog", fogIsDisabled ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "StereoMode", stereoscopyMode);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "StereoSwapEyes", stereoscopyEyeSwap ? "True" : "False");
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "StereoDepth", stereoscopySeparation);
+ NativeLibrary.SetConfig("gfx_opengl.ini", "Enhancements", "StereoConvergence", stereoscopyConvergence);
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/cpu/CPUSettingsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/cpu/CPUSettingsFragment.java
new file mode 100644
index 0000000000..134e1cdb65
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/cpu/CPUSettingsFragment.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings.cpu;
+
+import org.dolphinemu.dolphinemu.R;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.PreferenceFragment;
+
+/**
+ * Responsible for the loading of the CPU preferences.
+ */
+public final class CPUSettingsFragment extends PreferenceFragment
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.cpu_prefs);
+
+ final ListPreference cpuCores = (ListPreference) findPreference("cpuCorePref");
+
+ //
+ // Set valid emulation cores depending on the CPU architecture
+ // that the Android device is running on.
+ //
+ if (Build.CPU_ABI.contains("x86-64"))
+ {
+ cpuCores.setEntries(R.array.emuCoreEntriesX86_64);
+ cpuCores.setEntryValues(R.array.emuCoreValuesX86_64);
+ }
+ else if (Build.CPU_ABI.contains("arm64"))
+ {
+ cpuCores.setEntries(R.array.emuCoreEntriesARM64);
+ cpuCores.setEntryValues(R.array.emuCoreValuesARM64);
+ }
+ else if (Build.CPU_ABI.contains("arm"))
+ {
+ cpuCores.setEntries(R.array.emuCoreEntriesARM);
+ cpuCores.setEntryValues(R.array.emuCoreValuesARM);
+ }
+ else
+ {
+ cpuCores.setEntries(R.array.emuCoreEntriesOther);
+ cpuCores.setEntryValues(R.array.emuCoreValuesOther);
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/custom/UpdatingListPreference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/custom/UpdatingListPreference.java
new file mode 100644
index 0000000000..273b5fe547
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/custom/UpdatingListPreference.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings.custom;
+
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+/**
+ * A {@link ListPreference} that updates its summary upon it's selection being changed.
+ */
+public final class UpdatingListPreference extends ListPreference
+{
+ /**
+ * Constructor
+ *
+ * @param context the current {@link Context}.
+ */
+ public UpdatingListPreference(Context context, AttributeSet attribs)
+ {
+ super(context, attribs);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult)
+ {
+ super.onDialogClosed(positiveResult);
+
+ // If an entry was selected
+ if (positiveResult)
+ {
+ setSummary(getEntry());
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputBindingPreference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputBindingPreference.java
new file mode 100644
index 0000000000..e182f93a5c
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputBindingPreference.java
@@ -0,0 +1,55 @@
+package org.dolphinemu.dolphinemu.settings.input;
+
+import org.dolphinemu.dolphinemu.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.preference.Preference;
+import android.util.AttributeSet;
+
+/**
+ * {@link Preference} subclass that represents a preference
+ * used for assigning a key bind.
+ */
+public final class InputBindingPreference extends Preference
+{
+ /**
+ * Constructor that is called when inflating an InputBindingPreference from XML.
+ *
+ * @param context The current {@link Context}.
+ * @param attrs The attributes of the XML tag that is inflating the preference.
+ */
+ public InputBindingPreference(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onClick()
+ {
+ // Begin the creation of the input alert.
+ final MotionAlertDialog dialog = new MotionAlertDialog(getContext(), this);
+
+ // Set the cancel button.
+ dialog.setButton(AlertDialog.BUTTON_NEGATIVE, getContext().getString(R.string.cancel), new AlertDialog.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ // Do nothing. Just makes the cancel button show up.
+ }
+ });
+
+ // Set the title and description message.
+ dialog.setTitle(R.string.input_binding);
+ dialog.setMessage(String.format(getContext().getString(R.string.input_binding_descrip), getTitle()));
+
+ // Don't allow the dialog to close when a user taps
+ // outside of it. They must press cancel or provide an input.
+ dialog.setCanceledOnTouchOutside(false);
+
+ // Everything is set, show the dialog.
+ dialog.show();
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputConfigFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputConfigFragment.java
new file mode 100644
index 0000000000..82e2ffc44f
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/InputConfigFragment.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings.input;
+
+import android.app.Fragment;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.view.InputDevice;
+import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.R;
+
+import java.util.List;
+
+/**
+ * The {@link Fragment} responsible for implementing the functionality
+ * within the input control mapping config.
+ */
+public final class InputConfigFragment extends PreferenceFragment
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // Expand the preferences from the XML.
+ addPreferencesFromResource(R.xml.input_prefs);
+
+ // Set the summary messages of the preferences to whatever binding
+ // is currently set within the Dolphin config.
+ final String[] gamecubeKeys =
+ {
+ "InputA", "InputB", "InputX", "InputY", "InputZ", "InputStart",
+ "DPadUp", "DPadDown", "DPadLeft", "DPadRight",
+ "MainUp", "MainDown", "MainLeft", "MainRight",
+ "CStickUp", "CStickDown", "CStickLeft", "CStickRight",
+ "InputL", "InputR"
+ };
+
+ final String[] wiimoteKeys =
+ {
+ "WiimoteInputA", "WiimoteInputB", "WiimoteInputOne", "WiimoteInputTwo", "WiimoteInputPlus", "WiimoteInputMinus", "WiimoteInputHome",
+ "WiimoteIRUp", "WiimoteIRDown", "WiimoteIRLeft", "WiimoteIRRight", "WiimoteIRForward", "WiimoteIRBackward",
+ "WiimoteSwingUp", "WiimoteSwingDown", "WiimoteSwingLeft", "WiimoteSwingRight",
+ "WiimoteTiltForward", "WiimoteTiltBackward", "WiimoteTiltLeft", "WiimoteTiltRight",
+ "WiimoteShakeX", "WiimoteShakeY", "WiimoteShakeZ",
+ "WiimoteDPadUp", "WiimoteDPadDown", "WiimoteDPadLeft", "WiimoteDPadRight"
+ };
+
+ for (int i = 0; i < 4; i++)
+ {
+ // Loop through the keys for all 4 GameCube controllers.
+ for (String key : gamecubeKeys)
+ {
+ final String binding = NativeLibrary.GetConfig("Dolphin.ini", "Android", key+"_"+i, "None");
+ final Preference pref = findPreference(key+"_"+i);
+ pref.setSummary(binding);
+ }
+
+ // Loop through the keys for the Wiimote
+ /*for (String key : wiimoteKeys)
+ {
+ final String binding = NativeLibrary.GetConfig("Dolphin.ini", "Android", key+"_"+i, "None");
+ final Preference pref = findPreference(key+"_"+i);
+ pref.setSummary(binding);
+ }*/
+ }
+ }
+
+ /**
+ * Gets the descriptor for the given {@link InputDevice}.
+ *
+ * @param input The {@link InputDevice} to get the descriptor of.
+ *
+ * @return the descriptor for the given {@link InputDevice}.
+ */
+ public static String getInputDesc(InputDevice input)
+ {
+ if (input == null)
+ return "null"; // Happens when the InputDevice is from an unknown source
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ {
+ return input.getDescriptor();
+ }
+ else
+ {
+ List<InputDevice.MotionRange> motions = input.getMotionRanges();
+ StringBuilder fakeid = new StringBuilder();
+
+ for (InputDevice.MotionRange range : motions)
+ fakeid.append(range.getAxis());
+
+ return fakeid.toString();
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/MotionAlertDialog.java
new file mode 100644
index 0000000000..58d3a933a1
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/MotionAlertDialog.java
@@ -0,0 +1,127 @@
+package org.dolphinemu.dolphinemu.settings.input;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * {@link AlertDialog} derivative that listens for
+ * motion events from controllers and joysticks.
+ */
+final class MotionAlertDialog extends AlertDialog
+{
+ // The selected input preference
+ private final Preference inputPref;
+
+ private boolean firstEvent = true;
+ private final ArrayList<Float> m_values = new ArrayList<Float>();
+
+ /**
+ * Constructor
+ *
+ * @param ctx The current {@link Context}.
+ * @param inputPref The Preference to show this dialog for.
+ */
+ public MotionAlertDialog(Context ctx, Preference inputPref)
+ {
+ super(ctx);
+
+ this.inputPref = inputPref;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event)
+ {
+ Log.d("InputConfigFragment", "Received key event: " + event.getAction());
+ switch (event.getAction())
+ {
+ case KeyEvent.ACTION_DOWN:
+ case KeyEvent.ACTION_UP:
+ InputDevice input = event.getDevice();
+ String bindStr = "Device '" + InputConfigFragment.getInputDesc(input) + "'-Button " + event.getKeyCode();
+ NativeLibrary.SetConfig("Dolphin.ini", "Android", inputPref.getKey(), bindStr);
+ inputPref.setSummary(bindStr);
+ dismiss();
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+
+ // Method that will be called within dispatchGenericMotionEvent
+ // that handles joystick/controller movements.
+ private boolean onMotionEvent(MotionEvent event)
+ {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
+ return false;
+
+ Log.d("InputConfigFragment", "Received motion event: " + event.getAction());
+
+ InputDevice input = event.getDevice();
+ List<InputDevice.MotionRange> motions = input.getMotionRanges();
+ if (firstEvent)
+ {
+ m_values.clear();
+
+ for (InputDevice.MotionRange range : motions)
+ {
+ m_values.add(event.getAxisValue(range.getAxis()));
+ }
+
+ firstEvent = false;
+ }
+ else
+ {
+ for (int a = 0; a < motions.size(); ++a)
+ {
+ InputDevice.MotionRange range = motions.get(a);
+
+ if (m_values.get(a) > (event.getAxisValue(range.getAxis()) + 0.5f))
+ {
+ String bindStr = "Device '" + InputConfigFragment.getInputDesc(input) + "'-Axis " + range.getAxis() + "-";
+ NativeLibrary.SetConfig("Dolphin.ini", "Android", inputPref.getKey(), bindStr);
+ inputPref.setSummary(bindStr);
+ dismiss();
+ }
+ else if (m_values.get(a) < (event.getAxisValue(range.getAxis()) - 0.5f))
+ {
+ String bindStr = "Device '" + InputConfigFragment.getInputDesc(input) + "'-Axis " + range.getAxis() + "+";
+ NativeLibrary.SetConfig("Dolphin.ini", "Android", inputPref.getKey(), bindStr);
+ inputPref.setSummary(bindStr);
+ dismiss();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event)
+ {
+ if (onKeyDown(event.getKeyCode(), event))
+ return true;
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event)
+ {
+ if (onMotionEvent(event))
+ return true;
+
+ return super.dispatchGenericMotionEvent(event);
+ }
+} \ No newline at end of file
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigActivity.java
new file mode 100644
index 0000000000..b5e4fc8086
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigActivity.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings.input.overlayconfig;
+
+import org.dolphinemu.dolphinemu.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.RelativeLayout;
+
+/**
+ * {@link Activity} used for configuring the input overlay.
+ */
+public final class OverlayConfigActivity extends Activity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // Initialize all of the buttons to add.
+ final OverlayConfigButton buttonA = new OverlayConfigButton(this, "gcpad_a", R.drawable.gcpad_a);
+ final OverlayConfigButton buttonB = new OverlayConfigButton(this, "gcpad_b", R.drawable.gcpad_b);
+ final OverlayConfigButton buttonX = new OverlayConfigButton(this, "gcpad_x", R.drawable.gcpad_x);
+ final OverlayConfigButton buttonY = new OverlayConfigButton(this, "gcpad_y", R.drawable.gcpad_y);
+ final OverlayConfigButton buttonZ = new OverlayConfigButton(this, "gcpad_z", R.drawable.gcpad_z);
+ final OverlayConfigButton buttonS = new OverlayConfigButton(this, "gcpad_start", R.drawable.gcpad_start);
+ final OverlayConfigButton buttonL = new OverlayConfigButton(this, "gcpad_l", R.drawable.gcpad_l);
+ final OverlayConfigButton buttonR = new OverlayConfigButton(this, "gcpad_r", R.drawable.gcpad_r);
+ final OverlayConfigButton joystick = new OverlayConfigButton(this, "gcpad_joystick_range", R.drawable.gcpad_joystick_range);
+
+ // Add the buttons to the layout
+ final RelativeLayout configLayout = new RelativeLayout(this);
+ configLayout.addView(buttonA);
+ configLayout.addView(buttonB);
+ configLayout.addView(buttonX);
+ configLayout.addView(buttonY);
+ configLayout.addView(buttonZ);
+ configLayout.addView(buttonS);
+ configLayout.addView(buttonL);
+ configLayout.addView(buttonR);
+ configLayout.addView(joystick);
+
+ // Now set the layout
+ setContentView(configLayout);
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigButton.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigButton.java
new file mode 100644
index 0000000000..0d4767eec4
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/input/overlayconfig/OverlayConfigButton.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings.input.overlayconfig;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.preference.PreferenceManager;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.Button;
+import org.dolphinemu.dolphinemu.R;
+
+/**
+ * A movable {@link Button} for use within the
+ * input overlay configuration screen.
+ */
+public final class OverlayConfigButton extends Button implements OnTouchListener
+{
+ // SharedPreferences instance that the button positions are cached to.
+ private final SharedPreferences sharedPrefs;
+
+ // The String ID for this button.
+ //
+ // This ID is used upon releasing this button as the key for saving
+ // the X and Y coordinates of this button. This same key is also used
+ // for setting the coordinates of the button on the actual overlay during emulation.
+ //
+ // They can be accessed through SharedPreferences respectively as follows:
+ //
+ // SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ // float buttonX = sPrefs.getFloat(buttonId+"-X", -1f);
+ // float buttonY = sPrefs.getFloat(buttonId+"-Y", -1f);
+ //
+ private final String buttonId;
+
+ // The offset of the press while moving the button
+ private float moveOffsetX, moveOffsetY;
+
+ private Drawable resizeDrawable(Drawable image, float scale)
+ {
+ // Retrieve screen dimensions.
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+
+ Bitmap b = ((BitmapDrawable)image).getBitmap();
+ Bitmap bitmapResized = Bitmap.createScaledBitmap(b,
+ (int)(displayMetrics.heightPixels * scale),
+ (int)(displayMetrics.heightPixels * scale),
+ true);
+
+ return new BitmapDrawable(getResources(), bitmapResized);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param context the current {@link Context}.
+ * @param buttonId the String ID for this button.
+ * @param drawableId the Drawable ID for the image to represent this OverlayConfigButton.
+ */
+ public OverlayConfigButton(Context context, String buttonId, int drawableId)
+ {
+ super(context);
+
+ // Set the button ID.
+ this.buttonId = buttonId;
+
+ // Set the button as its own OnTouchListener.
+ setOnTouchListener(this);
+
+ // Get the SharedPreferences instance.
+ sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Decide scale based on button ID
+
+ // SeekBars are not able to set a minimum value, only a maximum value, which complicates
+ // things a bit. What happens here is after the SeekBar's value is retrieved, 25 is
+ // added so the value will never go below 25. It is then divided by 50 (25 + 25) so the
+ // default value will be 100%.
+ float scale;
+ float overlaySize = sharedPrefs.getInt("controls_size", 25);
+ overlaySize += 25;
+ overlaySize /= 50;
+
+ switch (drawableId)
+ {
+ case R.drawable.gcpad_b:
+ scale = 0.13f * overlaySize;
+ break;
+ case R.drawable.gcpad_x:
+ case R.drawable.gcpad_y:
+ scale = 0.18f * overlaySize;
+ break;
+ case R.drawable.gcpad_start:
+ scale = 0.12f * overlaySize;
+ break;
+ case R.drawable.gcpad_joystick_range:
+ scale = 0.30f * overlaySize;
+ break;
+ default:
+ scale = 0.20f * overlaySize;
+ break;
+ }
+
+ // Set the button's icon that represents it.
+ setBackground(resizeDrawable(getResources().getDrawable(drawableId), scale));
+
+ // Check if this button has previous values set that aren't the default.
+ final float x = sharedPrefs.getFloat(buttonId+"-X", -1f);
+ final float y = sharedPrefs.getFloat(buttonId+"-Y", -1f);
+
+ // If they are not -1, then they have a previous value set.
+ // Thus, we set those coordinate values.
+ if (x != -1f && y != -1f)
+ {
+ setX(x);
+ setY(y);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event)
+ {
+ switch(event.getAction())
+ {
+ // Get the offset of the press within the button
+ // The event X and Y locations are the offset within the button, not the screen
+ case MotionEvent.ACTION_DOWN:
+ {
+ moveOffsetX = event.getX();
+ moveOffsetY = event.getY();
+ return true;
+ }
+
+ // Only change the X/Y coordinates when we move the button.
+ case MotionEvent.ACTION_MOVE:
+ {
+ setX(getX() + event.getX() - moveOffsetX);
+ setY(getY() + event.getY() - moveOffsetY);
+ return true;
+ }
+
+ // Whenever the press event has ended
+ // is when we save all of the information.
+ case MotionEvent.ACTION_UP:
+ {
+ // Add the current X and Y positions of this button into SharedPreferences.
+ SharedPreferences.Editor editor = sharedPrefs.edit();
+ editor.putFloat(buttonId+"-X", getX());
+ editor.putFloat(buttonId+"-Y", getY());
+ editor.apply();
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/video/VideoSettingsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/video/VideoSettingsFragment.java
new file mode 100644
index 0000000000..214612f895
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/settings/video/VideoSettingsFragment.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.settings.video;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.os.Environment;
+import android.preference.ListPreference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+
+import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.utils.EGLHelper;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Responsible for handling the loading of the video preferences.
+ */
+public final class VideoSettingsFragment extends PreferenceFragment
+{
+ private final EGLHelper eglHelper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT);
+ private final String vendor = eglHelper.getGL().glGetString(GL10.GL_VENDOR);
+ private final String version = eglHelper.getGL().glGetString(GL10.GL_VERSION);
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.video_prefs);
+
+ //
+ // Setting valid video backends.
+ //
+ final ListPreference videoBackends = (ListPreference) findPreference("gpuPref");
+ final boolean deviceSupportsGL = eglHelper.supportsOpenGL();
+ final boolean deviceSupportsGLES3 = eglHelper.supportsGLES3();
+
+ if (deviceSupportsGL)
+ {
+ videoBackends.setEntries(R.array.videoBackendEntriesGL);
+ videoBackends.setEntryValues(R.array.videoBackendValuesGL);
+ }
+ else if (deviceSupportsGLES3)
+ {
+ videoBackends.setEntries(R.array.videoBackendEntriesGLES3);
+ videoBackends.setEntryValues(R.array.videoBackendValuesGLES3);
+ }
+ else
+ {
+ videoBackends.setEntries(R.array.videoBackendEntriesNoGLES3);
+ videoBackends.setEntryValues(R.array.videoBackendValuesNoGLES3);
+ }
+
+ //
+ // Set available post processing shaders
+ //
+
+ List<CharSequence> shader_names = new ArrayList<CharSequence>();
+ List<CharSequence> shader_values = new ArrayList<CharSequence>();
+
+ // Disabled option
+ shader_names.add("Disabled");
+ shader_values.add("");
+
+ File shaders_folder = new File(Environment.getExternalStorageDirectory()+ File.separator+"dolphin-emu"+ File.separator+"Shaders");
+ if (shaders_folder.exists())
+ {
+ File[] shaders = shaders_folder.listFiles();
+ for (File file : shaders)
+ {
+ if (file.isFile())
+ {
+ String filename = file.getName();
+ if (filename.contains(".glsl"))
+ {
+ // Strip the extension and put it in to the list
+ shader_names.add(filename.substring(0, filename.lastIndexOf('.')));
+ shader_values.add(filename.substring(0, filename.lastIndexOf('.')));
+ }
+ }
+ }
+ }
+
+ final ListPreference shader_preference = (ListPreference) findPreference("postProcessingShader");
+ shader_preference.setEntries(shader_names.toArray(new CharSequence[shader_names.size()]));
+ shader_preference.setEntryValues(shader_values.toArray(new CharSequence[shader_values.size()]));
+
+ //
+ // Disable all options if Software Rendering is used.
+ //
+ // Note that the numeric value in 'getPreference()'
+ // denotes the placement on the UI. So if more elements are
+ // added to the video settings, these may need to change.
+ //
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ final PreferenceScreen mainScreen = getPreferenceScreen();
+
+ if (videoBackends.getValue().equals("Software Renderer"))
+ {
+ mainScreen.getPreference(0).setEnabled(false);
+ mainScreen.getPreference(1).setEnabled(false);
+ mainScreen.getPreference(3).setEnabled(false);
+ }
+ else if (videoBackends.getValue().equals("OGL"))
+ {
+ mainScreen.getPreference(0).setEnabled(true);
+ mainScreen.getPreference(1).setEnabled(true);
+ mainScreen.getPreference(3).setEnabled(true);
+
+ // Check if we support stereo
+ // If we support desktop GL then we must support at least OpenGL 3.2
+ // If we only support OpenGLES then we need both OpenGLES 3.1 and AEP
+ if ((eglHelper.supportsOpenGL() && eglHelper.GetVersion() >= 320) ||
+ (eglHelper.supportsGLES3() && eglHelper.GetVersion() >= 310 && eglHelper.SupportsExtension("GL_ANDROID_extension_pack_es31a")))
+ mainScreen.findPreference("StereoscopyScreen").setEnabled(true);
+ else
+ mainScreen.findPreference("StereoscopyScreen").setEnabled(false);
+ }
+
+ // Also set a listener, so that if someone changes the video backend, it will disable
+ // the video settings, upon the user choosing "Software Rendering".
+ sPrefs.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener()
+ {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences preference, String key)
+ {
+ if (key.equals("gpuPref"))
+ {
+ if (preference.getString(key, "Software Renderer").equals("Software Renderer"))
+ {
+ mainScreen.getPreference(0).setEnabled(false);
+ mainScreen.getPreference(1).setEnabled(false);
+ mainScreen.getPreference(3).setEnabled(false);
+ }
+ else if (preference.getString(key, "Software Renderer").equals("OGL"))
+ {
+ mainScreen.getPreference(0).setEnabled(true);
+ mainScreen.getPreference(1).setEnabled(true);
+ mainScreen.getPreference(3).setEnabled(true);
+
+ // Create an alert telling them that their phone sucks
+ if (eglHelper.supportsGLES3()
+ && vendor.equals("Qualcomm")
+ && getQualcommVersion() == 14.0f)
+ {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.device_compat_warning);
+ builder.setMessage(R.string.device_gles3compat_warning_msg);
+ builder.setPositiveButton(R.string.yes, null);
+ builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which)
+ {
+ // Get an editor.
+ SharedPreferences.Editor editor = sPrefs.edit();
+ editor.putString("gpuPref", "Software Renderer");
+ editor.apply();
+ videoBackends.setValue("Software Renderer");
+ videoBackends.setSummary("Software Renderer");
+ }
+ });
+ builder.show();
+ }
+ }
+ }
+ }
+ });
+ }
+
+ private float getQualcommVersion()
+ {
+ final int start = version.indexOf("V@") + 2;
+ final StringBuilder versionBuilder = new StringBuilder();
+
+ for (int i = start; i < version.length(); i++)
+ {
+ char c = version.charAt(i);
+
+ // End of numeric portion of version string.
+ if (c == ' ')
+ break;
+
+ versionBuilder.append(c);
+ }
+
+ return Float.parseFloat(versionBuilder.toString());
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuAdapter.java
new file mode 100644
index 0000000000..60fd8b56f3
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuAdapter.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.sidemenu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.List;
+
+import org.dolphinemu.dolphinemu.R;
+
+/**
+ * Adapter that backs the sidebar menu.
+ * <p>
+ * Responsible for handling the elements of each sidebar item.
+ */
+public final class SideMenuAdapter extends ArrayAdapter<SideMenuItem>
+{
+ private final Context context;
+ private final int id;
+ private final List<SideMenuItem>items;
+
+ /**
+ * Constructor
+ *
+ * @param context The current {@link Context}.
+ * @param resourceId The resource ID for a layout file containing a layout to use when instantiating views.
+ * @param objects The objects to represent in the {@link ListView}.
+ */
+ public SideMenuAdapter(Context context, int resourceId, List<SideMenuItem> objects)
+ {
+ super(context, resourceId, objects);
+
+ this.context = context;
+ this.id = resourceId;
+ this.items = objects;
+ }
+
+ @Override
+ public SideMenuItem getItem(int i)
+ {
+ return items.get(i);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent)
+ {
+ if (convertView == null)
+ {
+ LayoutInflater vi = LayoutInflater.from(context);
+ convertView = vi.inflate(id, null);
+ }
+
+ final SideMenuItem item = items.get(position);
+ if (item != null)
+ {
+ TextView title = (TextView) convertView.findViewById(R.id.SideMenuTitle);
+
+ if (title != null)
+ title.setText(item.getName());
+ }
+
+ return convertView;
+ }
+}
+
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuItem.java
new file mode 100644
index 0000000000..402fad094c
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/sidemenu/SideMenuItem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.sidemenu;
+
+
+/**
+ * Represents an item that goes in the sidemenu of the app.
+ */
+public final class SideMenuItem
+{
+ private final String name;
+ private final int id;
+
+ /**
+ * Constructor
+ *
+ * @param name The name of the SideMenuItem.
+ * @param id ID number of this specific SideMenuItem.
+ */
+ public SideMenuItem(String name, int id)
+ {
+ this.name = name;
+ this.id = id;
+ }
+
+ /**
+ * Gets the name of this SideMenuItem.
+ *
+ * @return the name of this SideMenuItem.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Gets the ID of this SideMenuItem.
+ *
+ * @return the ID of this SideMenuItem.
+ */
+ public int getID()
+ {
+ return id;
+ }
+}
+
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CPUHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CPUHelper.java
new file mode 100644
index 0000000000..30be6e2213
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CPUHelper.java
@@ -0,0 +1,395 @@
+package org.dolphinemu.dolphinemu.utils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import org.dolphinemu.dolphinemu.R;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+/**
+ * Utility class for retrieving information
+ * from a device's CPU.
+ */
+public final class CPUHelper
+{
+ private int revision;
+ private int variant;
+ private int numCores;
+ private String implementerID = "N/A";
+ private String part = "N/A";
+ private String hardware = "N/A";
+ private String processorInfo = "N/A";
+ private String features = "N/A";
+
+ private final Context ctx;
+
+ /**
+ * Constructor
+ *
+ * @param ctx The current {@link Context}.
+ */
+ public CPUHelper(Context ctx)
+ {
+ this.ctx = ctx;
+
+ try
+ {
+ // TODO: Should do other architectures as well (x86 and MIPS).
+ // Can do differentiating between platforms by using
+ // android.os.Build.CPU_ABI.
+ //
+ // CPU_ABI.contains("armeabi") == get ARM info.
+ // CPU_ABI.contains("x86") == get x86 info.
+ // CPU_ABI.contains("mips") == get MIPS info.
+ //
+ // However additional testing should be done across devices,
+ // I highly doubt /proc/cpuinfo retains the same formatting
+ // on different architectures.
+ //
+ // If push comes to shove, we can simply spit out the cpuinfo
+ // contents. I would like to avoid this if possible, however.
+
+ if (Build.CPU_ABI.contains("arm"))
+ {
+ getARMInfo();
+ }
+ else
+ {
+ Log.e("CPUHelper", "CPU architecture not supported yet.");
+ }
+ }
+ catch (IOException ioe)
+ {
+ Log.e("CPUHelper", ioe.getMessage());
+ }
+ }
+
+ /**
+ * Gets the revision number of the CPU.
+ *
+ * @return the revision number of the CPU.
+ */
+ public int getRevision()
+ {
+ return revision;
+ }
+
+
+ /**
+ * Gets the CPU variant number.
+ *
+ * @return the CPU variant number.
+ */
+ public int getVariant()
+ {
+ return variant;
+ }
+
+ /**
+ * Gets the total number of cores in the CPU.
+ *
+ * @return the total number of cores in the CPU.
+ */
+ public int getNumCores()
+ {
+ return numCores;
+ }
+
+ /**
+ * Gets the name of the implementer of the CPU.
+ *
+ * @return the name of the implementer of the CPU.
+ */
+ public String getImplementer()
+ {
+ return implementerID;
+ }
+
+ /**
+ * Gets the specific processor type of the CPU.
+ *
+ * @return the specific processor type.
+ */
+ public String getProcessorType()
+ {
+ return part;
+ }
+
+ /**
+ * Gets the internal name of the hardware.
+ *
+ * @return the internal name of the hardware.
+ */
+ public String getHardware()
+ {
+ return hardware;
+ }
+
+ /**
+ * Get the processor info string.
+ *
+ * @return the processor info string.
+ */
+ public String getProcessorInfo()
+ {
+ return processorInfo;
+ }
+
+ /**
+ * Gets the features supported by the CPU.
+ *
+ * @return the features supported by the CPU.
+ */
+ public String getFeatures()
+ {
+ return features;
+ }
+
+ /**
+ * Whether or not this CPU is using the ARM architecture.
+ *
+ * @return true if this CPU uses the ARM architecture; false otherwise.
+ */
+ public static boolean isARM()
+ {
+ return Build.CPU_ABI.contains("arm");
+ }
+
+ /**
+ * Whether or not this CPU is using the ARM64 architecture.
+ *
+ * @return true if this CPU uses the ARM64 architecture; false otherwise.
+ */
+ public static boolean isARM64() { return Build.CPU_ABI.contains("arm64"); }
+
+ /**
+ * Whether or not this CPU is using the x86 architecture.
+ *
+ * @return true if this CPU uses the x86 architecture; false otherwise.
+ */
+ public static boolean isX86()
+ {
+ return Build.CPU_ABI.contains("x86");
+ }
+
+ /**
+ * Whether or not this CPU is using the MIPS architecture.
+ *
+ * @return true if this CPU uses the MIPS architecture; false otherwise.
+ */
+ public static boolean isMIPS()
+ {
+ return Build.CPU_ABI.contains("mips");
+ }
+
+ // Retrieves information for ARM CPUs.
+ private void getARMInfo() throws IOException
+ {
+ File info = new File("/proc/cpuinfo");
+ if (info.exists())
+ {
+ BufferedReader br = new BufferedReader(new FileReader(info));
+
+ String line;
+ while ((line = br.readLine()) != null)
+ {
+ if (line.contains("Processor\t:"))
+ {
+ this.processorInfo = parseLine(line);
+ }
+ else if (line.contains("Hardware\t:"))
+ {
+ this.hardware = parseLine(line);
+ }
+ else if (line.contains("Features\t:"))
+ {
+ this.features = parseLine(line);
+ }
+ else if (line.contains("CPU implementer\t:"))
+ {
+ this.implementerID = parseArmID(Integer.decode(parseLine(line)));
+ }
+ else if (line.contains("CPU part\t:"))
+ {
+ this.part = parseArmPartNumber(Integer.decode(parseLine(line)));
+ }
+ else if (line.contains("CPU revision\t:"))
+ {
+ this.revision = Integer.decode(parseLine(line));
+ }
+ else if (line.contains("CPU variant\t:"))
+ {
+ this.variant = Integer.decode(parseLine(line));
+ }
+ else if (line.contains("processor\t:")) // Lower case indicates a specific core number
+ {
+ this.numCores++;
+ }
+ }
+
+ br.close();
+ }
+ }
+
+ // Basic function for parsing cpuinfo format strings.
+ // cpuinfo format strings consist of [label:info] parts.
+ // We only want to retrieve the info portion so we split
+ // them using ':' as a delimeter.
+ private String parseLine(String line)
+ {
+ String[] temp = line.split(":");
+ if (temp.length != 2)
+ return "N/A";
+
+ return temp[1].trim();
+ }
+
+ // Parses an ARM CPU ID.
+ private String parseArmID(int id)
+ {
+ switch (id)
+ {
+ case 0x41:
+ return "ARM Limited";
+
+ case 0x44:
+ return "Digital Equipment Corporation";
+
+ case 0x4D:
+ return "Freescale Semiconductor Inc.";
+
+ case 0x4E:
+ return "Nvidia Corporation";
+
+ case 0x51:
+ return "Qualcomm Inc.";
+
+ case 0x56:
+ return "Marvell Semiconductor Inc.";
+
+ case 0x69:
+ return "Intel Corporation";
+
+ default:
+ return "N/A";
+ }
+ }
+
+ // Parses the ARM CPU Part number.
+ private String parseArmPartNumber(int partNum)
+ {
+ switch (partNum)
+ {
+ // Qualcomm part numbers.
+ case 0x00F:
+ return "Qualcomm Scorpion";
+
+ case 0x02D:
+ return "Qualcomm Dual Scorpion";
+
+ case 0x04D:
+ return "Qualcomm Dual Krait";
+
+ case 0x06F:
+ return "Qualcomm Quad Krait";
+
+ // Marvell Semiconductor part numbers
+ case 0x131:
+ return "Marvell Feroceon";
+
+ case 0x581:
+ return "Marvell PJ4/PJ4b";
+
+ case 0x584:
+ return "Marvell Dual PJ4/PJ4b";
+
+ // Official ARM part numbers.
+ case 0x920:
+ return "ARM920";
+
+ case 0x922:
+ return "ARM922";
+
+ case 0x926:
+ return "ARM926";
+
+ case 0x940:
+ return "ARM940";
+
+ case 0x946:
+ return "ARM946";
+
+ case 0x966:
+ return "ARM966";
+
+ case 0x968:
+ return "ARM968";
+
+ case 0xB02:
+ return "ARM11 MPCore";
+
+ case 0xB36:
+ return "ARM1136";
+
+ case 0xB56:
+ return "ARM1156";
+
+ case 0xB76:
+ return "ARM1176";
+
+ case 0xC05:
+ return "ARM Cortex A5";
+
+ case 0xC07:
+ return "ARM Cortex-A7 MPCore";
+
+ case 0xC08:
+ return "ARM Cortex A8";
+
+ case 0xC09:
+ return "ARM Cortex A9";
+
+ case 0xC0C:
+ return "ARM Cortex A12";
+
+ case 0xC0F:
+ return "ARM Cortex A15";
+
+ case 0xC14:
+ return "ARM Cortex R4";
+
+ case 0xC15:
+ return "ARM Cortex R5";
+
+ case 0xC20:
+ return "ARM Cortex M0";
+
+ case 0xC21:
+ return "ARM Cortex M1";
+
+ case 0xC23:
+ return "ARM Cortex M3";
+
+ case 0xC24:
+ return "ARM Cortex M4";
+
+ case 0xC60:
+ return "ARM Cortex M0+";
+
+ case 0xD03:
+ return "ARM Cortex A53";
+
+ case 0xD07:
+ return "ARM Cortex A57 MPCore";
+
+
+ default: // Unknown/Not yet added to list.
+ return String.format(ctx.getString(R.string.unknown_part_num), partNum);
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/EGLHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/EGLHelper.java
new file mode 100644
index 0000000000..4eef5f6a3d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/EGLHelper.java
@@ -0,0 +1,393 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2
+ * Refer to the license.txt file included.
+ */
+
+package org.dolphinemu.dolphinemu.utils;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLES30;
+import android.util.Log;
+
+import org.dolphinemu.dolphinemu.NativeLibrary;
+
+/**
+ * Utility class that abstracts all the stuff about
+ * EGL initialization out of the way if all that is
+ * wanted is to query the underlying GL API for information.
+ */
+public final class EGLHelper
+{
+ private final EGL10 mEGL;
+ private final EGLDisplay mDisplay;
+ private EGLConfig[] mEGLConfigs;
+ private EGLContext mEGLContext;
+ private EGLSurface mEGLSurface;
+ private GL10 mGL;
+
+ // GL support flags
+ private boolean supportGL;
+ private boolean supportGLES2;
+ private boolean supportGLES3;
+
+ // Renderable type bitmasks
+ public static final int EGL_OPENGL_ES_BIT = 0x0001;
+ public static final int EGL_OPENGL_ES2_BIT = 0x0004;
+ public static final int EGL_OPENGL_BIT = 0x0008;
+ public static final int EGL_OPENGL_ES3_BIT_KHR = 0x0040;
+
+ // API types
+ public static final int EGL_OPENGL_ES_API = 0x30A0;
+ public static final int EGL_OPENGL_API = 0x30A2;
+
+ /**
+ * Constructor
+ * <p>
+ * Initializes the underlying {@link EGLSurface} with a width and height of 1.
+ * This is useful if all you need to use this class for is to query information
+ * from specific API contexts.
+ *
+ * @param renderableType Bitmask indicating which types of client API contexts
+ * the framebuffer config must support.
+ */
+ public EGLHelper(int renderableType)
+ {
+ this(1, 1, renderableType);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param width Width of the underlying {@link EGLSurface}.
+ * @param height Height of the underlying {@link EGLSurface}.
+ * @param renderableType Bitmask indicating which types of client API contexts
+ * the framebuffer config must support.
+ */
+ public EGLHelper(int width, int height, int renderableType)
+ {
+ // Initialize handle to an EGL display.
+ mEGL = (EGL10) EGLContext.getEGL();
+ mDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+ // If a display is present, initialize EGL.
+ if (mDisplay != EGL10.EGL_NO_DISPLAY)
+ {
+ int[] version = new int[2];
+ if (mEGL.eglInitialize(mDisplay, version))
+ {
+ // Detect supported GL APIs, initialize configs, etc.
+ detect();
+
+ // Create context and surface
+ create(width, height, renderableType);
+ }
+ else
+ {
+ Log.e("EGLHelper", "Error initializing EGL.");
+ }
+ }
+ else
+ {
+ Log.e("EGLHelper", "Error initializing EGL display.");
+ }
+ }
+
+ /**
+ * Releases all resources associated with this helper.
+ * <p>
+ * This should be called whenever this helper is no longer needed.
+ */
+ public void closeHelper()
+ {
+ mEGL.eglTerminate(mDisplay);
+ }
+
+ /**
+ * Gets information through EGL.<br/>
+ * <p>
+ * Index 0: Vendor <br/>
+ * Index 1: Version <br/>
+ * Index 2: Renderer <br/>
+ * Index 3: Extensions <br/>
+ *
+ * @return information retrieved through EGL.
+ */
+ public String[] getEGLInfo()
+ {
+ String[] info = {
+ mGL.glGetString(GL10.GL_VENDOR),
+ mGL.glGetString(GL10.GL_VERSION),
+ mGL.glGetString(GL10.GL_RENDERER),
+ mGL.glGetString(GL10.GL_EXTENSIONS),
+ };
+
+ return info;
+ }
+
+ /**
+ * Whether or not this device supports OpenGL.
+ *
+ * @return true if this device supports OpenGL; false otherwise.
+ */
+ public boolean supportsOpenGL()
+ {
+ return supportGL;
+ }
+
+ /**
+ * Whether or not this device supports OpenGL ES 2.
+ * <br/>
+ * Note that if this returns true, then OpenGL ES 1 is also supported.
+ *
+ * @return true if this device supports OpenGL ES 2; false otherwise.
+ */
+ public boolean supportsGLES2()
+ {
+ return supportGLES2;
+ }
+
+ /**
+ * Whether or not this device supports OpenGL ES 3.
+ * <br/>
+ * Note that if this returns true, then OpenGL ES 1 and 2 are also supported.
+ *
+ * @return true if this device supports OpenGL ES 3; false otherwise.
+ */
+ public boolean supportsGLES3()
+ {
+ return supportGLES3;
+ }
+
+ /**
+ * Gets the underlying {@link EGL10} instance.
+ *
+ * @return the underlying {@link EGL10} instance.
+ */
+ public EGL10 getEGL()
+ {
+ return mEGL;
+ }
+
+ /**
+ * Gets the underlying {@link GL10} instance.
+ *
+ * @return the underlying {@link GL10} instance.
+ */
+ public GL10 getGL()
+ {
+ return mGL;
+ }
+
+ /**
+ * Gets the underlying {@link EGLDisplay}.
+ *
+ * @return the underlying {@link EGLDisplay}
+ */
+ public EGLDisplay getDisplay()
+ {
+ return mDisplay;
+ }
+
+ /**
+ * Gets all supported framebuffer configurations for this device.
+ *
+ * @return all supported framebuffer configurations for this device.
+ */
+ public EGLConfig[] getConfigs()
+ {
+ return mEGLConfigs;
+ }
+
+ /**
+ * Gets the underlying {@link EGLContext}.
+ *
+ * @return the underlying {@link EGLContext}.
+ */
+ public EGLContext getContext()
+ {
+ return mEGLContext;
+ }
+
+ /**
+ * Gets the underlying {@link EGLSurface}.
+ *
+ * @return the underlying {@link EGLSurface}.
+ */
+ public EGLSurface getSurface()
+ {
+ return mEGLSurface;
+ }
+
+ // Detects the specific kind of GL modes that are supported
+ private boolean detect()
+ {
+ // Get total number of configs available.
+ int[] numConfigs = new int[1];
+ if (!mEGL.eglGetConfigs(mDisplay, null, 0, numConfigs))
+ {
+ Log.e("EGLHelper", "Error retrieving number of EGL configs available.");
+ return false;
+ }
+
+ // Now get all the configurations
+ mEGLConfigs = new EGLConfig[numConfigs[0]];
+ if (!mEGL.eglGetConfigs(mDisplay, mEGLConfigs, mEGLConfigs.length, numConfigs))
+ {
+ Log.e("EGLHelper", "Error retrieving all EGL configs.");
+ return false;
+ }
+
+ for (int i = 0; i < mEGLConfigs.length; i++)
+ {
+ int[] attribVal = new int[1];
+ boolean ret = mEGL.eglGetConfigAttrib(mDisplay, mEGLConfigs[i], EGL10.EGL_RENDERABLE_TYPE, attribVal);
+ if (ret)
+ {
+ if ((attribVal[0] & EGL_OPENGL_BIT) != 0)
+ supportGL = true;
+
+ if ((attribVal[0] & EGL_OPENGL_ES2_BIT) != 0)
+ supportGLES2 = true;
+
+ if ((attribVal[0] & EGL_OPENGL_ES3_BIT_KHR) != 0)
+ supportGLES3 = true;
+ }
+ }
+
+ return true;
+ }
+
+ // Creates the context and surface.
+ private void create(int width, int height, int renderableType)
+ {
+ int[] attribs = {
+ EGL10.EGL_WIDTH, width,
+ EGL10.EGL_HEIGHT, height,
+ EGL10.EGL_NONE
+ };
+
+ // Initially we just assume GLES2 will be the default context.
+ int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ int[] ctx_attribs = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL10.EGL_NONE
+ };
+
+ // Determine the type of context that will be created
+ // and change the attribute arrays accordingly.
+ switch (renderableType)
+ {
+ case EGL_OPENGL_ES_BIT:
+ ctx_attribs[1] = 1;
+ break;
+
+ case EGL_OPENGL_BIT:
+ ctx_attribs[0] = EGL10.EGL_NONE;
+ break;
+
+ case EGL_OPENGL_ES3_BIT_KHR:
+ ctx_attribs[1] = 3;
+ break;
+
+ case EGL_OPENGL_ES2_BIT:
+ default: // Fall-back to GLES 2.
+ ctx_attribs[1] = 2;
+ break;
+ }
+ if (renderableType == EGL_OPENGL_BIT)
+ NativeLibrary.eglBindAPI(EGL_OPENGL_API);
+ else
+ NativeLibrary.eglBindAPI(EGL_OPENGL_ES_API);
+
+ mEGLContext = mEGL.eglCreateContext(mDisplay, mEGLConfigs[0], EGL10.EGL_NO_CONTEXT, ctx_attribs);
+ mEGLSurface = mEGL.eglCreatePbufferSurface(mDisplay, mEGLConfigs[0], attribs);
+ mEGL.eglMakeCurrent(mDisplay, mEGLSurface, mEGLSurface, mEGLContext);
+ mGL = (GL10) mEGLContext.getGL();
+ }
+
+ /**
+ * Simplified call to {@link GL10#glGetString(int)}
+ * <p>
+ * Accepts the following constants:
+ * <ul>
+ * <li>GL_VENDOR - Company responsible for the GL implementation.</li>
+ * <li>GL_VERSION - Version or release number.</li>
+ * <li>GL_RENDERER - Name of the renderer</li>
+ * <li>GL_SHADING_LANGUAGE_VERSION - Version or release number of the shading language </li>
+ * </ul>
+ *
+ * @param glEnum A symbolic constant within {@link GL10}.
+ *
+ * @return the string information represented by {@code glEnum}.
+ */
+ public String glGetString(int glEnum)
+ {
+ return mGL.glGetString(glEnum);
+ }
+
+ /**
+ * Simplified call to {@link GLES30#glGetStringi(int, int)}
+ * <p>
+ * Accepts the following constants:
+ * <ul>
+ * <li>GL_VENDOR - Company responsible for the GL implementation.</li>
+ * <li>GL_VERSION - Version or release number.</li>
+ * <li>GL_RENDERER - Name of the renderer</li>
+ * <li>GL_SHADING_LANGUAGE_VERSION - Version or release number of the shading language </li>
+ * <li>GL_EXTENSIONS - Extension string supported by the implementation at {@code index}.</li>
+ * </ul>
+ *
+ * @param glEnum A symbolic GL constant
+ * @param index The index of the string to return.
+ *
+ * @return the string information represented by {@code glEnum} and {@code index}.
+ */
+ public String glGetStringi(int glEnum, int index)
+ {
+ return GLES30.glGetStringi(glEnum, index);
+ }
+
+ public boolean SupportsExtension(String extension)
+ {
+ int[] num_ext = new int[1];
+ GLES30.glGetIntegerv(GLES30.GL_NUM_EXTENSIONS, num_ext, 0);
+
+ for (int i = 0; i < num_ext[0]; ++i)
+ {
+ String ext = GLES30.glGetStringi(GLES30.GL_EXTENSIONS, i);
+ if (ext.equals(extension))
+ return true;
+ }
+ return false;
+ }
+
+ public int GetVersion()
+ {
+ int[] major = new int[1];
+ int[] minor = new int[1];
+ GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, major, 0);
+ GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, minor, 0);
+ return major[0] * 100 + minor[0] * 10;
+ }
+
+ /**
+ * Simplified call to {@link GL10#glGetIntegerv(int, int[], int)
+ *
+ * @param glEnum A symbolic GL constant.
+ *
+ * @return the integer information represented by {@code glEnum}.
+ */
+ public int glGetInteger(int glEnum)
+ {
+ int[] val = new int[1];
+ mGL.glGetIntegerv(glEnum, val, 0);
+ return val[0];
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java
new file mode 100644
index 0000000000..eab6f74ff0
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/SliderPreference.java
@@ -0,0 +1,94 @@
+package org.dolphinemu.dolphinemu.utils;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import org.dolphinemu.dolphinemu.R;
+
+public class SliderPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener, View.OnClickListener
+{
+ private static final String androidns = "http://schemas.android.com/apk/res/android";
+
+ // SeekBar
+ private int m_max, m_value;
+ private SeekBar m_seekbar;
+
+ // TextView
+ private TextView m_textview;
+
+ public SliderPreference(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+
+ // Seekbar values
+ m_value = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
+ m_max = attrs.getAttributeIntValue(androidns, "max", 100);
+ }
+
+ @Override
+ protected View onCreateDialogView()
+ {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ LinearLayout layout = (LinearLayout)inflater.inflate(R.layout.slider_layout, null, false);
+
+ m_seekbar = (SeekBar)layout.findViewById(R.id.sliderSeekBar);
+ m_textview = (TextView)layout.findViewById(R.id.sliderTextView);
+
+ if (shouldPersist())
+ m_value = Integer.valueOf(getPersistedString(Integer.toString(m_value)));
+
+ m_seekbar.setMax(m_max);
+ m_seekbar.setProgress(m_value);
+ setProgressText(m_value);
+ m_seekbar.setOnSeekBarChangeListener(this);
+
+ return layout;
+ }
+
+ // SeekBar overrides
+ @Override
+ public void onProgressChanged(SeekBar seek, int value, boolean fromTouch)
+ {
+ m_value = value;
+ setProgressText(value);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seek) {}
+ @Override
+ public void onStopTrackingTouch(SeekBar seek) {}
+
+ void setProgressText(int value)
+ {
+ m_textview.setText(String.valueOf(value));
+ }
+
+ @Override
+ public void showDialog(Bundle state)
+ {
+ super.showDialog(state);
+
+ Button positiveButton = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
+ positiveButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v)
+ {
+ if (shouldPersist())
+ {
+ persistString(Integer.toString(m_seekbar.getProgress()));
+ callChangeListener(m_seekbar.getProgress());
+ }
+ ((AlertDialog) getDialog()).dismiss();
+ }
+}