diff mbox

[Branch,~glmark2-dev/glmark2/trunk] Rev 231: Android: Create a GUI for defining and running benchmarks.

Message ID 20120710100423.31872.37891.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

alexandros.frantzis@linaro.org July 10, 2012, 10:04 a.m. UTC
Merge authors:
  Alexandros Frantzis (afrantzis)
Related merge proposals:
  https://code.launchpad.net/~linaro-graphics-wg/glmark2/android-gui/+merge/113942
  proposed by: Alexandros Frantzis (afrantzis)
  review: Approve - Jesse Barker (jesse-barker)
------------------------------------------------------------
revno: 231 [merge]
committer: Alexandros Frantzis <alexandros.frantzis@linaro.org>
branch nick: trunk
timestamp: Tue 2012-07-10 12:47:01 +0300
message:
  Android: Create a GUI for defining and running benchmarks.
added:
  android/build.xml
  android/res/layout/
  android/res/layout/activity_editor.xml
  android/res/layout/activity_main.xml
  android/res/layout/list_header.xml
  android/res/layout/list_item.xml
  android/src/org/linaro/glmark2/EditorActivity.java
  android/src/org/linaro/glmark2/GLVisualConfig.java
  android/src/org/linaro/glmark2/Glmark2Native.java
  android/src/org/linaro/glmark2/MainActivity.java
  android/src/org/linaro/glmark2/SceneInfo.java
renamed:
  android/default.properties => android/project.properties
modified:
  android/AndroidManifest.xml
  android/res/values/strings.xml
  android/src/org/linaro/glmark2/Glmark2SurfaceView.java
  src/android.cpp
  android/project.properties


--
lp:glmark2
https://code.launchpad.net/~glmark2-dev/glmark2/trunk

You are subscribed to branch lp:glmark2.
To unsubscribe from this branch go to https://code.launchpad.net/~glmark2-dev/glmark2/trunk/+edit-subscription
diff mbox

Patch

=== modified file 'android/AndroidManifest.xml'
--- android/AndroidManifest.xml	2012-06-21 12:57:43 +0000
+++ android/AndroidManifest.xml	2012-07-03 14:19:27 +0000
@@ -3,14 +3,28 @@ 
       android:versionCode="1"
       android:versionName="2012.06" package="org.linaro.glmark2">
     <application android:label="@string/app_name">
-        <activity android:label="@string/app_name"
+        <activity android:name="org.linaro.glmark2.MainActivity"
+                  android:label="@string/title_activity_main">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="org.linaro.glmark2.EditorActivity"
+            android:label="@string/title_activity_editor" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <activity android:label="@string/title_activity_glmark2"
                   android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                   android:launchMode="singleTask"
                   android:screenOrientation="nosensor"
+                  android:process=":glmark2"
                   android:name="org.linaro.glmark2.Glmark2Activity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
     </application>

=== added file 'android/build.xml'
--- android/build.xml	1970-01-01 00:00:00 +0000
+++ android/build.xml	2012-07-03 12:18:59 +0000
@@ -0,0 +1,92 @@ 
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="Glmark2" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+        <isset property="env.ANDROID_HOME" />
+    </condition>
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+            unless="sdk.dir"
+    />
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>

=== renamed file 'android/default.properties' => 'android/project.properties'
--- android/default.properties	2012-07-09 15:54:30 +0000
+++ android/project.properties	2012-07-10 09:47:01 +0000
@@ -3,9 +3,12 @@ 
 #
 # This file must be checked in Version Control Systems.
 #
-# To customize properties used by the Ant build system use,
-# "build.properties", and override values to adapt the script to your
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
 # project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
 target=android-10

=== added directory 'android/res/layout'
=== added file 'android/res/layout/activity_editor.xml'
--- android/res/layout/activity_editor.xml	1970-01-01 00:00:00 +0000
+++ android/res/layout/activity_editor.xml	2012-07-06 13:31:01 +0000
@@ -0,0 +1,34 @@ 
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" >
+
+    <LinearLayout android:id="@+id/buttonLinearLayout" 
+                  android:layout_width="match_parent" 
+                  android:layout_height="wrap_content" 
+                  android:layout_alignParentBottom="true">
+
+        <Button android:id="@+id/runButton"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1.0"
+                android:layout_alignParentBottom="true"
+                android:text="Run" />
+
+        <Button android:id="@+id/saveButton"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1.0"
+                android:layout_alignParentBottom="true"
+                android:text="Save" />
+
+    </LinearLayout>
+
+
+    <ListView android:id="@+id/editorListView"
+              android:layout_above="@id/buttonLinearLayout"
+              android:layout_weight="1.0"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout_alignParentTop="true" />
+</RelativeLayout>

=== added file 'android/res/layout/activity_main.xml'
--- android/res/layout/activity_main.xml	1970-01-01 00:00:00 +0000
+++ android/res/layout/activity_main.xml	2012-07-06 13:31:01 +0000
@@ -0,0 +1,20 @@ 
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" >
+
+    <Button android:id="@+id/runButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:text="Run" />
+
+    <ListView android:id="@+id/benchmarkListView"
+              android:layout_above="@id/runButton"
+              android:layout_weight="1.0"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout_alignParentTop="true" />
+
+
+</RelativeLayout>

=== added file 'android/res/layout/list_header.xml'
--- android/res/layout/list_header.xml	1970-01-01 00:00:00 +0000
+++ android/res/layout/list_header.xml	2012-07-06 13:01:37 +0000
@@ -0,0 +1,5 @@ 
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          style="?android:attr/listSeparatorTextViewStyle"
+          android:id="@+id/listHeader" />

=== added file 'android/res/layout/list_item.xml'
--- android/res/layout/list_item.xml	1970-01-01 00:00:00 +0000
+++ android/res/layout/list_item.xml	2012-07-06 13:31:01 +0000
@@ -0,0 +1,32 @@ 
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minHeight="?android:attr/listPreferredItemHeight"
+                android:gravity="center_vertical"
+                android:paddingRight="?android:attr/scrollbarSize" >
+
+    <RelativeLayout android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="15dip"
+                    android:layout_marginRight="6dip"
+                    android:layout_marginTop="6dip"
+                    android:layout_marginBottom="6dip"
+                    android:layout_weight="1">
+
+        <TextView android:id="@+id/title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        <TextView android:id="@+id/summary"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_alignLeft="@id/title"
+                  android:layout_below="@id/title"
+                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:textColor="?android:attr/textColorSecondary" />
+
+    </RelativeLayout>
+
+</LinearLayout>

=== modified file 'android/res/values/strings.xml'
--- android/res/values/strings.xml	2011-08-12 10:34:06 +0000
+++ android/res/values/strings.xml	2012-07-03 14:19:27 +0000
@@ -1,4 +1,7 @@ 
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="app_name">GLMark2</string>
+    <string name="title_activity_main">GLMark2</string>
+    <string name="title_activity_editor">GLMark2 Benchmark Editor</string>
+    <string name="title_activity_glmark2">GLMark2</string>
 </resources>

=== added file 'android/src/org/linaro/glmark2/EditorActivity.java'
--- android/src/org/linaro/glmark2/EditorActivity.java	1970-01-01 00:00:00 +0000
+++ android/src/org/linaro/glmark2/EditorActivity.java	2012-07-10 09:44:06 +0000
@@ -0,0 +1,522 @@ 
+/*
+ * Copyright © 2012 Linaro Limited
+ *
+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
+ *
+ * glmark2 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * glmark2.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *  Alexandros Frantzis
+ */
+package org.linaro.glmark2;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+public class EditorActivity extends Activity {
+    public static final int DIALOG_SCENE_NAME_ID = 0;
+    public static final int DIALOG_SCENE_OPTION_ID = 1;
+
+    public static final int ITEM_POSITION_SCENE_NAME_HEADER = 0;
+    public static final int ITEM_POSITION_SCENE_NAME = 1;
+    public static final int ITEM_POSITION_SCENE_OPTION_HEADER = 2;
+    public static final int ITEM_POSITION_SCENE_OPTION = 3;
+
+    private EditorItemAdapter adapter;
+    private ArrayList<SceneInfo> sceneInfoList;
+    private String[] sceneNames;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_editor);
+
+        /* Get information about the available scenes */
+        sceneInfoList = getSceneInfoList();
+        sceneNames = getSceneNames();
+
+        /* Read information sent by the main activity */
+        final int benchmarkPos = this.getIntent().getIntExtra("benchmark-pos", 0);
+        String benchmarkText = getIntent().getStringExtra("benchmark-text");
+        if (benchmarkText.isEmpty())
+            benchmarkText = sceneNames[0];
+
+        /* Set up the run button */
+        Button runButton = (Button) findViewById(R.id.runButton);
+        runButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Intent intent = new Intent(EditorActivity.this, Glmark2Activity.class);
+                String args = "-b " + getBenchmarkDescriptionText();
+                intent.putExtra("args", args);
+                startActivity(intent);
+            }
+        });
+
+        /* Set up the save button */
+        Button button = (Button) findViewById(R.id.saveButton);
+        button.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                String newBenchmarkText = getBenchmarkDescriptionText();
+                Intent intent = new Intent();
+                intent.putExtra("benchmark-text", newBenchmarkText);
+                intent.putExtra("benchmark-pos", benchmarkPos);
+                setResult(RESULT_OK, intent);
+                finish();
+            }
+        });
+
+        /* Set up list view */
+        ListView lv = (ListView) findViewById(R.id.editorListView);
+        adapter = new EditorItemAdapter(this, R.layout.list_item,
+                                        getEditorItemList(benchmarkText));
+        lv.setAdapter(adapter);
+
+        lv.setOnItemClickListener(new OnItemClickListener() {
+            public void onItemClick(AdapterView<?> parentView, View childView, int position, long id) {
+                Bundle bundle = new Bundle();
+                bundle.putInt("item-pos", position);
+                /* Show the right dialog, depending on the clicked list position */
+                if (position == ITEM_POSITION_SCENE_NAME)
+                    showDialog(DIALOG_SCENE_NAME_ID, bundle);
+                else if (position >= ITEM_POSITION_SCENE_OPTION)
+                    showDialog(DIALOG_SCENE_OPTION_ID, bundle);
+            }
+        });
+
+        lv.setOnItemLongClickListener(new OnItemLongClickListener() {
+            public boolean onItemLongClick(AdapterView<?> parentView, View childView, int position, long id) {
+                /* Reset the value of the long-clicked option */
+                if (position >= ITEM_POSITION_SCENE_OPTION) {
+                    EditorItem item = adapter.getItem(position);
+                    item.value = null;
+                    adapter.notifyDataSetChanged();
+                }
+                return true;
+            }
+        });
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle bundle) {
+        final int itemPos = bundle.getInt("item-pos");
+        Dialog dialog;
+        final int finalId = id;
+
+        switch (id) {
+            case DIALOG_SCENE_NAME_ID:
+                {
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                builder.setTitle("Pick a scene");
+                builder.setItems(sceneNames, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int item) {
+                        adapter.clear();
+                        for (EditorItem ei: getEditorItemList(sceneNames[item]))
+                            adapter.add(ei);
+                        adapter.notifyDataSetChanged();
+                        dismissDialog(DIALOG_SCENE_NAME_ID);
+                    }
+                });
+                dialog = builder.create();
+                }
+                break;
+
+            case DIALOG_SCENE_OPTION_ID:
+                {
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                final EditorItem item = adapter.getItem(itemPos);
+                final EditText input = new EditText(this);
+                if (item.value != null)
+                    input.setText(item.value);
+
+                input.setOnEditorActionListener(new OnEditorActionListener() {
+                    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                        if (actionId == EditorInfo.IME_ACTION_DONE ||
+                            (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER &&
+                             event.getAction() == KeyEvent.ACTION_UP))
+                        {
+                            item.value = v.getText().toString();
+                            dismissDialog(DIALOG_SCENE_OPTION_ID);
+                        }
+                        return true;
+                    }
+                });
+                builder.setTitle(item.option.name + ": " + item.option.description);
+                dialog = builder.create();
+                ((AlertDialog)dialog).setView(input, 15, 6, 15, 6);
+                dialog.getWindow().setSoftInputMode(
+                        WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN |
+                        WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
+                        );
+
+                }
+                break;
+
+            default:
+                dialog = null;
+                break;
+        }
+
+        if (dialog != null) {
+            dialog.setOnDismissListener(new OnDismissListener() {
+                public void onDismiss(DialogInterface dialog) {
+                    removeDialog(finalId);
+                }
+            });
+        }
+
+        return dialog;
+    }
+
+    /**
+     * Gets the value of an option.
+     *
+     * @param benchArray an array of option strings ("opt=val")
+     * @param opt the options to get the value of
+     *
+     * @return the value or null
+     */
+    private String getOptionValue(String[] benchArray, String opt) {
+        String ret = null;
+
+        /* Search from the end to the beginning */
+        for (int n = benchArray.length - 1; n >= 0; n--) {
+            String s = benchArray[n].trim();
+            if (s.startsWith(opt + "=")) {
+                int i = s.indexOf('=');
+                if (i >= 0 && i + 1 < s.length()) {
+                    ret = s.substring(i + 1).trim();
+                    break;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Gets the benchmark description string of the current editing state.
+     *
+     * @return the string
+     */
+    private String getBenchmarkDescriptionText() {
+        String ret = "";
+
+        for (int i = 0; i < adapter.getCount(); i++) {
+            /* Convert each list item to a proper string representation */
+            EditorItem item = adapter.getItem(i);
+            if (item == null)
+                continue;
+
+            String s = "";
+
+            /*
+             * Append "opt=" if this is an option item, except the
+             * "__custom__" item.
+             */
+            if (item.option != null && item.value != null &&
+                !item.option.name.equals("__custom__"))
+            {
+                s += item.option.name + "=";
+            }
+
+            /*
+             * Append the item value if this is not "__custom__".
+             */
+            if (item.value != null && !item.value.equals("__custom__"))
+                s += item.value;
+
+            /*
+             * Append ":" to the description string if needed.
+             */
+            if (!s.isEmpty() && !ret.isEmpty())
+                ret += ":";
+
+            /* Append the item representation */
+            ret += s;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Creates an EditorItem list from a benchmark description string.
+     *
+     * @param benchDesc the benchmark description string
+     *
+     * @return the list
+     */
+    private ArrayList<EditorItem> getEditorItemList(String benchDesc) {
+        String[] benchArray = benchDesc.split(":");
+        String benchName = benchArray[0].trim();
+
+        if (benchName.isEmpty())
+            benchName = "__custom__";
+
+        /* Find SceneInfo from name */
+        SceneInfo sceneInfo = null;
+        for (SceneInfo si: sceneInfoList) {
+            if (si.name.equals(benchName)) {
+                sceneInfo = si;
+                break;
+            }
+        }
+
+        /* If we couldn't find a matching SceneInfo, use __custom__ */
+        if (sceneInfo == null) {
+            for (SceneInfo si: sceneInfoList) {
+                if (si.name.equals("__custom__")) {
+                    sceneInfo = si;
+                    break;
+                }
+            }
+        }
+
+        ArrayList<EditorItem> l = new ArrayList<EditorItem>();
+
+        /* Append null item for Scene header */
+        l.add(null);
+
+        /* Append scene name item */
+        l.add(new EditorItem(null, sceneInfo.name));
+
+        /* Append null item for Options header */
+        l.add(null);
+
+        /* Append items to the list */
+        if (!sceneInfo.name.equals("__custom__")) {
+            /* Append scene option items */
+            for (SceneInfo.Option opt: sceneInfo.options)
+                l.add(new EditorItem(opt, getOptionValue(benchArray, opt.name)));
+        }
+        else {
+            String desc = new String(benchDesc);
+            if (desc.startsWith("__custom__"))
+                desc = "";
+
+            /* Append scene option items (only one for __custom__) */
+            for (SceneInfo.Option opt: sceneInfo.options)
+                l.add(new EditorItem(opt, desc));
+        }
+
+        return l;
+    }
+
+    /**
+     * Gets a list of information about the available scenes.
+     *
+     * @return the list
+     */
+    private ArrayList<SceneInfo> getSceneInfoList() {
+        ArrayList<SceneInfo> l = new ArrayList<SceneInfo>();
+        SceneInfo customSceneInfo = new SceneInfo("__custom__");
+        customSceneInfo.addOption("__custom__", "Custom benchmark string", "");
+
+        for (Parcelable p: getIntent().getParcelableArrayExtra("scene-info"))
+            l.add((SceneInfo)p);
+
+        /* Sort SceneInfo list by name */
+        Collections.sort(l, new Comparator<SceneInfo>() {
+            public int compare(SceneInfo s1, SceneInfo s2) {
+                return s1.name.compareTo(s2.name);
+            }
+        });
+
+        /* Add the "__custom__" SceneInfo */
+        l.add(customSceneInfo);
+
+        return l;
+    }
+
+    /**
+     * Gets the array of scene names.
+     *
+     * @return the array
+     */
+    private String[] getSceneNames() {
+        ArrayList<String> l = new ArrayList<String>();
+
+        for (SceneInfo si: sceneInfoList) {
+            if (!si.name.isEmpty())
+                l.add(si.name);
+        }
+
+        String[] a = new String[0];
+        return l.toArray(a);
+    }
+
+
+    static private class EditorItem {
+        SceneInfo.Option option;
+
+        public EditorItem(SceneInfo.Option o, String value) {
+            this.option = o;
+            this.value = value;
+        }
+
+        public String value;
+    }
+
+    /**
+     * A ListView adapter that creates list item views from EditorItems
+     */
+    private class EditorItemAdapter extends ArrayAdapter<EditorItem> {
+        static final int VIEW_TYPE_HEADER = 0;
+        static final int VIEW_TYPE_SCENE_NAME = 1;
+        static final int VIEW_TYPE_SCENE_OPTION = 2;
+        static final int VIEW_TYPE_COUNT = 3;
+
+        public ArrayList<EditorItem> items;
+
+        public EditorItemAdapter(Context context, int textViewResourceId,
+                                 ArrayList<EditorItem> items)
+        {
+            super(context, textViewResourceId, items);
+            this.items = items;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return position == ITEM_POSITION_SCENE_NAME ||
+                   position >= ITEM_POSITION_SCENE_OPTION;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            if (position == ITEM_POSITION_SCENE_NAME)
+                return VIEW_TYPE_SCENE_NAME;
+            else if (position >= ITEM_POSITION_SCENE_OPTION)
+                return VIEW_TYPE_SCENE_OPTION;
+            else
+                return VIEW_TYPE_HEADER;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return VIEW_TYPE_COUNT;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            int viewType = getItemViewType(position);
+            View v = null;
+
+            if (viewType == VIEW_TYPE_HEADER)
+                v = getViewHeader(position, convertView);
+            else if (viewType == VIEW_TYPE_SCENE_NAME)
+                v = getViewScene(position, convertView);
+            else if (viewType == VIEW_TYPE_SCENE_OPTION)
+                v = getViewOption(position, convertView);
+
+            return v;
+        }
+
+        private View getViewHeader(int position, View convertView) {
+            /* Get the view/widget to use */
+            View v = convertView;
+            if (v == null) {
+                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                v = vi.inflate(R.layout.list_header, null);
+            }
+
+            TextView tv = (TextView) v;
+
+            if (position == ITEM_POSITION_SCENE_NAME_HEADER)
+                tv.setText("Scene");
+            else if (position == ITEM_POSITION_SCENE_OPTION_HEADER)
+                tv.setText("Options");
+
+            return tv;
+        }
+
+        private View getViewScene(int position, View convertView) {
+            /* Get the view/widget to use */
+            View v = convertView;
+            if (v == null) {
+                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                v = vi.inflate(R.layout.list_item, null);
+            }
+
+            EditorItem item = items.get(position);
+
+            TextView title = (TextView) v.findViewById(R.id.title);
+            TextView summary = (TextView) v.findViewById(R.id.summary);
+
+            if (title != null)
+                title.setText(item.value);
+            if (summary != null)
+                summary.setText("The scene to use");
+
+            return v;
+        }
+
+        private View getViewOption(int position, View convertView) {
+            /* Get the view/widget to use */
+            View v = convertView;
+            if (v == null) {
+                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                v = vi.inflate(R.layout.list_item, null);
+            }
+
+            EditorItem item = items.get(position);
+
+            TextView title = (TextView) v.findViewById(R.id.title);
+            TextView summary = (TextView) v.findViewById(R.id.summary);
+            boolean hasUserSetValue = item.value != null;
+            String value = hasUserSetValue ? item.value : item.option.defaultValue;
+
+            if (title != null) {
+                /* If the option has been edited by the user show it with emphasis */
+                SpannableString titleText = new SpannableString(item.option.name + " = " + value);
+                ForegroundColorSpan span = new ForegroundColorSpan(hasUserSetValue ? Color.CYAN : Color.LTGRAY);
+                titleText.setSpan(span, item.option.name.length() + " = ".length(), titleText.length(), 0);
+                title.setText(titleText);
+            }
+
+            if (summary != null)
+                summary.setText(item.option.description);
+
+            return v;
+        }
+    }
+}

=== added file 'android/src/org/linaro/glmark2/GLVisualConfig.java'
--- android/src/org/linaro/glmark2/GLVisualConfig.java	1970-01-01 00:00:00 +0000
+++ android/src/org/linaro/glmark2/GLVisualConfig.java	2012-07-04 14:27:42 +0000
@@ -0,0 +1,44 @@ 
+/*
+ * Copyright © 2012 Linaro Limited
+ *
+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
+ *
+ * glmark2 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * glmark2.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *  Alexandros Frantzis
+ */
+package org.linaro.glmark2;
+
+/** 
+ * Class that holds a configuration of a GL visual.
+ */
+class GLVisualConfig {
+    public GLVisualConfig() {}
+    public GLVisualConfig(int r, int g, int b, int a, int d, int buf) {
+        red = r;
+        green = g;
+        blue = b;
+        alpha = a;
+        depth = d;
+        buffer = buf;
+    }
+
+    public int red;
+    public int green;
+    public int blue;
+    public int alpha;
+    public int depth;
+    public int buffer;
+}

=== added file 'android/src/org/linaro/glmark2/Glmark2Native.java'
--- android/src/org/linaro/glmark2/Glmark2Native.java	1970-01-01 00:00:00 +0000
+++ android/src/org/linaro/glmark2/Glmark2Native.java	2012-07-04 14:38:56 +0000
@@ -0,0 +1,33 @@ 
+/*
+ * Copyright © 2012 Linaro Limited
+ *
+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
+ *
+ * glmark2 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * glmark2.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *  Alexandros Frantzis
+ */
+package org.linaro.glmark2;
+
+import android.content.res.AssetManager;
+
+class Glmark2Native {
+    public static native void init(AssetManager assetManager, String args);
+    public static native void resize(int w, int h);
+    public static native boolean render();
+    public static native void done();
+    public static native int scoreConfig(GLVisualConfig vc, GLVisualConfig target);
+    public static native SceneInfo[] getSceneInfo(AssetManager assetManager);
+}

=== modified file 'android/src/org/linaro/glmark2/Glmark2SurfaceView.java'
--- android/src/org/linaro/glmark2/Glmark2SurfaceView.java	2012-05-24 09:54:11 +0000
+++ android/src/org/linaro/glmark2/Glmark2SurfaceView.java	2012-07-04 14:27:42 +0000
@@ -1,9 +1,7 @@ 
-
 package org.linaro.glmark2;
 
 import android.graphics.PixelFormat;
 import android.opengl.GLSurfaceView;
-import android.content.res.AssetManager;
 import android.app.Activity;
 import android.util.Log;
 
@@ -12,29 +10,6 @@ 
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.opengles.GL10;
 
-/** 
- * Class that holds a configuration of a GL visual.
- */
-class GLVisualConfig {
-    public GLVisualConfig() {}
-    public GLVisualConfig(int r, int g, int b, int a, int d, int buf) {
-        red = r;
-        green = g;
-        blue = b;
-        alpha = a;
-        depth = d;
-        buffer = buf;
-    }
-
-    public int red;
-    public int green;
-    public int blue;
-    public int alpha;
-    public int depth;
-    public int buffer;
-}
-
-
 class Glmark2SurfaceView extends GLSurfaceView {
 
     public static final String LOG_TAG = "glmark2";
@@ -233,11 +208,3 @@ 
 
     private Glmark2SurfaceView mView;
 }
-
-class Glmark2Native {
-    public static native void init(AssetManager assetManager, String args);
-    public static native void resize(int w, int h);
-    public static native boolean render();
-    public static native void done();
-    public static native int scoreConfig(GLVisualConfig vc, GLVisualConfig target);
-}

=== added file 'android/src/org/linaro/glmark2/MainActivity.java'
--- android/src/org/linaro/glmark2/MainActivity.java	1970-01-01 00:00:00 +0000
+++ android/src/org/linaro/glmark2/MainActivity.java	2012-07-06 13:31:01 +0000
@@ -0,0 +1,302 @@ 
+/*
+ * Copyright © 2012 Linaro Limited
+ *
+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
+ *
+ * glmark2 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * glmark2.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *  Alexandros Frantzis
+ */
+package org.linaro.glmark2;
+
+import java.util.ArrayList;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.widget.BaseAdapter;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Button;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.util.Log;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.AdapterView;
+
+public class MainActivity extends Activity {
+    public static final int DIALOG_BENCHMARK_ACTIONS_ID = 0;
+
+    /**
+     * The supported benchmark item actions.
+     */
+    public enum BenchmarkItemAction {
+        EDIT, DELETE, CLONE, MOVEUP, MOVEDOWN
+    }
+
+    ArrayList<String> benchmarks;
+    BaseAdapter adapter;
+    SceneInfo[] sceneInfoList;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        ArrayList<String> savedBenchmarks = null;
+
+        if (savedInstanceState != null)
+            savedBenchmarks = savedInstanceState.getStringArrayList("benchmarks");
+
+        init(savedBenchmarks);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putStringArrayList("benchmarks", benchmarks);
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle bundle) {
+        final CharSequence[] benchmarkActions = {"Delete", "Clone", "Move Up", "Move Down"};
+        final BenchmarkItemAction[] benchmarkActionsId = {
+                BenchmarkItemAction.DELETE, BenchmarkItemAction.CLONE,
+                BenchmarkItemAction.MOVEUP, BenchmarkItemAction.MOVEDOWN
+        };
+        final int benchmarkPos = bundle.getInt("benchmark-pos");
+        final int finalId = id;
+
+        Dialog dialog;
+
+        switch (id) {
+            case DIALOG_BENCHMARK_ACTIONS_ID:
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                builder.setTitle("Pick an action");
+                builder.setItems(benchmarkActions, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int item) {
+                        doBenchmarkItemAction(benchmarkPos, benchmarkActionsId[item], null);
+                        dismissDialog(DIALOG_BENCHMARK_ACTIONS_ID);
+                    }
+                });
+                dialog = builder.create();
+                break;
+
+            default:
+                dialog = null;
+                break;
+        }
+
+        if (dialog != null) {
+            dialog.setOnDismissListener(new OnDismissListener() {
+                public void onDismiss(DialogInterface dialog) {
+                    removeDialog(finalId);
+                }
+            });
+        }
+
+        return dialog;
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode == RESULT_OK) {
+            String benchmarkText = data.getStringExtra("benchmark-text");
+            int benchmarkPos = data.getIntExtra("benchmark-pos", 0);
+            doBenchmarkItemAction(benchmarkPos, BenchmarkItemAction.EDIT, benchmarkText);
+        }
+    }
+
+    /**
+     * Initialize the activity.
+     *
+     * @param savedBenchmarks a list of benchmarks to load the list with (or null)
+     */
+    private void init(ArrayList<String> savedBenchmarks)
+    {
+        /* Fill in the benchmark list */
+        if (savedBenchmarks == null) {
+            benchmarks = new ArrayList<String>();
+            benchmarks.add("Add benchmark...");
+        }
+        else {
+            benchmarks = savedBenchmarks;
+        }
+
+        /* Get Scene information */
+        sceneInfoList = Glmark2Native.getSceneInfo(getAssets());
+
+        /* Set up the run button */
+        Button button = (Button) findViewById(R.id.runButton);
+        button.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Intent intent = new Intent(MainActivity.this, Glmark2Activity.class);
+                String args = "";
+                for (int i = 0; i < benchmarks.size() - 1; i++)
+                    args += "-b " + benchmarks.get(i) + " ";
+                if (!args.isEmpty())
+                    intent.putExtra("args", args);
+                startActivity(intent);
+            }
+        });
+
+        /* Set up the benchmark list view */
+        ListView lv = (ListView) findViewById(R.id.benchmarkListView);
+        adapter = new BenchmarkAdapter(this, R.layout.list_item, benchmarks);
+        lv.setAdapter(adapter);
+
+        lv.setOnItemClickListener(new OnItemClickListener() {
+            public void onItemClick(AdapterView<?> parentView, View childView, int position, long id) {
+                Intent intent = new Intent(MainActivity.this, EditorActivity.class);
+                String t = benchmarks.get(position);
+                if (position == benchmarks.size() - 1)
+                    t = "";
+                intent.putExtra("benchmark-text", t);
+                intent.putExtra("benchmark-pos", position);
+                intent.putExtra("scene-info", sceneInfoList);
+                startActivityForResult(intent, 1);
+            }
+        });
+
+        lv.setOnItemLongClickListener(new OnItemLongClickListener() {
+            public boolean onItemLongClick(AdapterView<?> parentView, View childView, int position, long id) {
+                if (position < benchmarks.size() - 1) {
+                    Bundle bundle = new Bundle();
+                    bundle.putInt("benchmark-pos", position);
+                    showDialog(DIALOG_BENCHMARK_ACTIONS_ID, bundle);
+                }
+                return true;
+            }
+        });
+
+    }
+
+    /**
+     * Perform an action on an listview benchmark item.
+     *
+     * @param position the position of the item in the listview
+     * @param action the action to perform
+     * @param data extra data needed by some actions
+     */
+    private void doBenchmarkItemAction(int position, BenchmarkItemAction action, String data)
+    {
+        int scrollPosition = position;
+
+        switch(action) {
+            case EDIT:
+                if (position == benchmarks.size() - 1) {
+                    benchmarks.add(position, data);
+                    scrollPosition = position + 1;
+                }
+                else {
+                    benchmarks.set(position, data);
+                }
+                break;
+            case DELETE:
+                benchmarks.remove(position);
+                break;
+            case CLONE:
+                {
+                    String s = benchmarks.get(position);
+                    benchmarks.add(position, s);
+                    scrollPosition = position + 1;
+                }
+                break;
+            case MOVEUP:
+                if (position > 0) {
+                    String up = benchmarks.get(position - 1);
+                    String s = benchmarks.get(position);
+                    benchmarks.set(position - 1, s);
+                    benchmarks.set(position, up);
+                    scrollPosition = position - 1;
+                }
+                break;
+            case MOVEDOWN:
+                if (position < benchmarks.size() - 2) {
+                    String down = benchmarks.get(position + 1);
+                    String s = benchmarks.get(position);
+                    benchmarks.set(position + 1, s);
+                    benchmarks.set(position, down);
+                    scrollPosition = position + 1;
+                }
+                break;
+            default:
+                break;
+        }
+
+
+        adapter.notifyDataSetChanged();
+
+        /* Scroll the list view so that the item of interest remains visible */
+        final int finalScrollPosition = scrollPosition;
+        final ListView lv = (ListView) findViewById(R.id.benchmarkListView);
+        lv.post(new Runnable() {
+            @Override
+            public void run() {
+                lv.smoothScrollToPosition(finalScrollPosition);
+            }
+        });
+    }
+
+    /**
+     * A ListView adapter that creates item views from benchmark strings.
+     */
+    private class BenchmarkAdapter extends ArrayAdapter<String> {
+        private ArrayList<String> items;
+
+        public BenchmarkAdapter(Context context, int textViewResourceId, ArrayList<String> items) {
+            super(context, textViewResourceId, items);
+            this.items = items;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            /* Get the view/widget to use */
+            View v = convertView;
+            if (v == null) {
+                LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                v = vi.inflate(R.layout.list_item, null);
+            }
+
+            /* Split the benchmark into its scene name and its options */
+            String benchmark = items.get(position);
+            String[] ba = benchmark.split(":", 2);
+
+            if (ba != null) {
+                TextView title = (TextView) v.findViewById(R.id.title);
+                TextView summary = (TextView) v.findViewById(R.id.summary);
+                title.setText("");
+                summary.setText("");
+
+                if (title != null && ba.length > 0)
+                    title.setText(ba[0]);
+                if (summary != null && ba.length > 1)
+                    summary.setText(ba[1]);
+            }
+            return v;
+        }
+    }
+
+    static {
+        System.loadLibrary("glmark2-android");
+    }
+}

=== added file 'android/src/org/linaro/glmark2/SceneInfo.java'
--- android/src/org/linaro/glmark2/SceneInfo.java	1970-01-01 00:00:00 +0000
+++ android/src/org/linaro/glmark2/SceneInfo.java	2012-07-05 15:05:49 +0000
@@ -0,0 +1,90 @@ 
+/*
+ * Copyright © 2012 Linaro Limited
+ *
+ * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark.
+ *
+ * glmark2 is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * glmark2.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *  Alexandros Frantzis
+ */
+package org.linaro.glmark2;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import java.util.ArrayList;
+
+class SceneInfo implements Parcelable {
+    static class Option {
+        String name;
+        String description;
+        String defaultValue;
+    }
+
+    public SceneInfo(String name) {
+        this.name = name;
+        this.options = new ArrayList<Option>();
+    }
+
+    public void addOption(String name, String description, String defaultValue) {
+        Option opt = new Option();
+        opt.name = name;
+        opt.description = description;
+        opt.defaultValue = defaultValue;
+        this.options.add(opt);
+    }
+
+    public String name;
+    public ArrayList<Option> options;
+
+    /* Parcelable interface */
+    public static final Parcelable.Creator<SceneInfo> CREATOR =
+        new Parcelable.Creator<SceneInfo>() {
+            public SceneInfo createFromParcel(Parcel in) {
+                return new SceneInfo(in);
+            }
+
+            public SceneInfo[] newArray(int size) {
+                return new SceneInfo[size];
+            }
+        };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(name);
+        out.writeInt(options.size());
+        for (Option opt: options) {
+            out.writeString(opt.name);
+            out.writeString(opt.description);
+            out.writeString(opt.defaultValue);
+        }
+    }
+
+    private SceneInfo(Parcel in) {
+        name = in.readString();
+        options = new ArrayList<Option>();
+
+        int size = in.readInt();
+        for (int i = 0; i < size; i++) {
+            Option opt = new Option();
+            opt.name = in.readString();
+            opt.description = in.readString();
+            opt.defaultValue = in.readString();
+            options.add(opt);
+        }
+    }
+}

=== modified file 'src/android.cpp'
--- src/android.cpp	2012-07-09 15:54:30 +0000
+++ src/android.cpp	2012-07-10 09:47:01 +0000
@@ -169,6 +169,78 @@ 
     vc.buffer = env->GetIntField(jvc, fid);
 }
 
+/** 
+ * Creates a SceneInfo Java object from a Scene.
+ * 
+ * @param env the JNIEnv
+ */
+static jobject
+scene_info_from_scene(JNIEnv *env, Scene &scene)
+{
+    jclass cls = env->FindClass("org/linaro/glmark2/SceneInfo");
+    jmethodID constructor = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
+    jmethodID add_option = env->GetMethodID(cls, "addOption", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+
+    /* Create the SceneInfo object */
+    jstring name = env->NewStringUTF(scene.name().c_str());
+    jobject scene_info = env->NewObject(cls, constructor, name);
+
+    const std::map<std::string, Scene::Option> &options = scene.options();
+
+    /* Add options to the SceneInfo object */
+    for (std::map<std::string, Scene::Option>::const_iterator opt_iter = options.begin();
+         opt_iter != options.end();
+         opt_iter++)
+    {
+        const Scene::Option &opt = opt_iter->second;
+        jstring opt_name = env->NewStringUTF(opt.name.c_str());
+        jstring opt_description = env->NewStringUTF(opt.description.c_str());
+        jstring opt_default_value = env->NewStringUTF(opt.default_value.c_str());
+
+        env->CallVoidMethod(scene_info, add_option,
+                            opt_name,
+                            opt_description,
+                            opt_default_value);
+
+        env->DeleteLocalRef(opt_name);
+        env->DeleteLocalRef(opt_description);
+        env->DeleteLocalRef(opt_default_value);
+    }
+
+    return scene_info;
+}
+
+class DummyCanvas : public Canvas {
+public:
+    DummyCanvas() : Canvas(0, 0) {}
+};
+
+/** 
+ * Creates all the available scenes and adds them to the supplied vector.
+ * 
+ * @param scenes the vector to add the scenes to
+ * @param canvas the canvas to create the scenes with
+ */
+static void
+create_and_add_scenes(std::vector<Scene*>& scenes, Canvas& canvas)
+{
+    scenes.push_back(new SceneDefaultOptions(canvas));
+    scenes.push_back(new SceneBuild(canvas));
+    scenes.push_back(new SceneTexture(canvas));
+    scenes.push_back(new SceneShading(canvas));
+    scenes.push_back(new SceneConditionals(canvas));
+    scenes.push_back(new SceneFunction(canvas));
+    scenes.push_back(new SceneLoop(canvas));
+    scenes.push_back(new SceneBump(canvas));
+    scenes.push_back(new SceneEffect2D(canvas));
+    scenes.push_back(new ScenePulsar(canvas));
+    scenes.push_back(new SceneDesktop(canvas));
+    scenes.push_back(new SceneBuffer(canvas));
+    scenes.push_back(new SceneIdeas(canvas));
+    scenes.push_back(new SceneTerrain(canvas));
+    scenes.push_back(new SceneJellyfish(canvas));
+}
+
 
 void
 Java_org_linaro_glmark2_native_init(JNIEnv* env, jclass clazz,
@@ -209,21 +281,17 @@ 
     Log::info("glmark2 %s\n", GLMARK_VERSION);
     g_canvas->print_info();
 
-    Benchmark::register_scene(*new SceneDefaultOptions(*g_canvas));
-    Benchmark::register_scene(*new SceneBuild(*g_canvas));
-    Benchmark::register_scene(*new SceneTexture(*g_canvas));
-    Benchmark::register_scene(*new SceneShading(*g_canvas));
-    Benchmark::register_scene(*new SceneConditionals(*g_canvas));
-    Benchmark::register_scene(*new SceneFunction(*g_canvas));
-    Benchmark::register_scene(*new SceneLoop(*g_canvas));
-    Benchmark::register_scene(*new SceneBump(*g_canvas));
-    Benchmark::register_scene(*new SceneEffect2D(*g_canvas));
-    Benchmark::register_scene(*new ScenePulsar(*g_canvas));
-    Benchmark::register_scene(*new SceneDesktop(*g_canvas));
-    Benchmark::register_scene(*new SceneBuffer(*g_canvas));
-    Benchmark::register_scene(*new SceneIdeas(*g_canvas));
-    Benchmark::register_scene(*new SceneTerrain(*g_canvas));
-    Benchmark::register_scene(*new SceneJellyfish(*g_canvas));
+    std::vector<Scene*> scenes;
+
+    /* Add and register scenes */
+    create_and_add_scenes(scenes, *g_canvas);
+
+    for (std::vector<Scene*>::const_iterator iter = scenes.begin();
+         iter != scenes.end();
+         iter++)
+    {
+        Benchmark::register_scene(**iter);
+    }
 
     g_benchmark_collection = new BenchmarkCollection();
     g_benchmark_collection->populate_from_options();
@@ -289,6 +357,42 @@ 
     return vc.match_score(target);
 }
 
+jobjectArray
+Java_org_linaro_glmark2_native_getSceneInfo(JNIEnv* env, jclass clazz,
+                                            jobject asset_manager)
+{
+    static_cast<void>(clazz);
+
+    Util::android_set_asset_manager(AAssetManager_fromJava(env, asset_manager));
+
+    std::vector<Scene*> scenes;
+    DummyCanvas canvas;
+    std::vector<jobject> si_vector;
+
+    create_and_add_scenes(scenes, canvas);
+
+    /* Create SceneInfo instances for all the scenes */
+    for (std::vector<Scene*>::const_iterator iter = scenes.begin();
+         iter != scenes.end();
+         iter++)
+    {
+        jobject si = scene_info_from_scene(env, **iter);
+        si_vector.push_back(si);
+    }
+
+    /* Create a SceneInfo[] array */
+    jclass si_cls = env->FindClass("org/linaro/glmark2/SceneInfo");
+    jobjectArray si_array = env->NewObjectArray(si_vector.size(), si_cls, 0);
+    
+    /* Populate the SceneInfo[] array */
+    for (size_t i = 0; i < si_vector.size(); i++)
+        env->SetObjectArrayElement(si_array, i, si_vector[i]);
+
+    Util::dispose_pointer_vector(scenes);
+
+    return si_array;
+}
+
 static JNINativeMethod glmark2_native_methods[] = {
     {
         "init",
@@ -314,6 +418,11 @@ 
         "scoreConfig",
         "(Lorg/linaro/glmark2/GLVisualConfig;Lorg/linaro/glmark2/GLVisualConfig;)I",
         reinterpret_cast<void*>(Java_org_linaro_glmark2_native_scoreConfig)
+    },
+    {
+        "getSceneInfo",
+        "(Landroid/content/res/AssetManager;)[Lorg/linaro/glmark2/SceneInfo;",
+        reinterpret_cast<void*>(Java_org_linaro_glmark2_native_getSceneInfo)
     }
 };