diff options
| author | Eder Bastos <bastos.eder@gmail.com> | 2015-04-30 18:15:10 -0400 |
|---|---|---|
| committer | Eder Bastos <bastos.eder@gmail.com> | 2015-05-02 21:49:17 -0400 |
| commit | c80225ea4d023ed91503521b74951db3a302ca20 (patch) | |
| tree | d69c8493aedf1226b9d4e9b447ef09364aaace84 /Source/Android/app/src/main/java | |
| parent | 4b00ccbe4ed79a3fd3369ca1b2972f0b8e4224ef (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')
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(); + } +} |
