Android: Implement a better benchmark editor that knows about the scenes and their options.
diff --git a/android/res/layout/activity_editor.xml b/android/res/layout/activity_editor.xml
index e04f742..c08acda 100644
--- a/android/res/layout/activity_editor.xml
+++ b/android/res/layout/activity_editor.xml
@@ -3,16 +3,20 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent" >
 
-    <EditText android:id="@+id/benchmarkEditorTextView"
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:layout_alignParentLeft="true"
-              android:layout_alignParentRight="true"
-              android:layout_alignParentTop="true"
-              android:gravity="top"
-              android:inputType="textMultiLine"
-              android:minLines="3">
-        <requestFocus/>
-    </EditText>
+    <Button android:id="@+id/saveButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:text="Save" />
 
+    <ListView android:id="@+id/editorListView"
+              android:layout_above="@id/saveButton"
+              android:layout_weight="1.0"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout_alignParentTop="true"
+              android:layout_marginLeft="6dip"
+              android:layout_marginRight="6dip"
+              android:layout_marginTop="6dip" 
+              android:layout_marginBottom="6dip" />
 </RelativeLayout>
diff --git a/android/res/layout/benchmark_item.xml b/android/res/layout/benchmark_item.xml
index 632bc5e..5a70988 100644
--- a/android/res/layout/benchmark_item.xml
+++ b/android/res/layout/benchmark_item.xml
@@ -11,9 +11,6 @@
     <TextView android:id="@+id/title"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:ellipsize="marquee"
-              android:fadingEdge="horizontal"
-              android:singleLine="true"
               android:textAppearance="?android:attr/textAppearanceLarge" />
 
     <TextView android:id="@+id/summary"
@@ -21,7 +18,6 @@
               android:layout_height="wrap_content"
               android:layout_alignLeft="@id/title"
               android:layout_below="@id/title"
-              android:maxLines="4"
               android:textAppearance="?android:attr/textAppearanceSmall" />
 
 </RelativeLayout>
diff --git a/android/src/org/linaro/glmark2/EditorActivity.java b/android/src/org/linaro/glmark2/EditorActivity.java
index ddf874b..c7ee64d 100644
--- a/android/src/org/linaro/glmark2/EditorActivity.java
+++ b/android/src/org/linaro/glmark2/EditorActivity.java
@@ -1,48 +1,442 @@
+/*
+ * 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.Bundle;
+import java.util.ArrayList;
+
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+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.KeyEvent;
 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;
-import android.content.Intent;
-import android.view.WindowManager;
 
 public class EditorActivity extends Activity {
+    public static final int DIALOG_SCENE_NAME_ID = 0;
+    public static final int DIALOG_SCENE_OPTION_ID = 1;
+
+    private EditorItemAdapter adapter;
+    private ArrayList<SceneInfo> sceneInfoList;
+    private String[] sceneNames;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_editor);
 
-        /* Show the soft-keyboard */
-        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        /* Get information about the available scenes */
+        sceneInfoList = getSceneInfoList();
+        sceneNames = getSceneNames();
 
-        /* Get the benchmark position as sent by the main activity */
+        /* 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 the textview widget text */
-        TextView tv = (TextView) findViewById(R.id.benchmarkEditorTextView);
-        tv.setText(getIntent().getStringExtra("benchmark-text"));
+        /* 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();
+            }
+        });
 
-        /* Handle editor events */
-        tv.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))
-                {
-                    /* Return result to caller activity */
-                    Intent intent = new Intent();
-                    intent.putExtra("benchmark-text", v.getText().toString());
-                    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.benchmark_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 == 0)
+                    showDialog(DIALOG_SCENE_NAME_ID, bundle);
+                else
+                    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 > 0) {
+                    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;
+
+        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();
+                        removeDialog(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();
+                            removeDialog(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);
+                }
+                break;
+
+            default:
+                dialog = null;
+                break;
+        }
+
+        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);
+            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 items to the list */
+        if (!sceneInfo.name.equals("__custom__")) {
+            /* Append scene name item */
+            l.add(new EditorItem(null, sceneInfo.name));
+
+            /* 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 name item */
+            l.add(new EditorItem(null, sceneInfo.name));
+
+            /* 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);
+
+        /* 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_SCENE_NAME = 0;
+        static final int VIEW_TYPE_SCENE_OPTION = 1;
+        static final int VIEW_TYPE_COUNT = 2;
+
+        public ArrayList<EditorItem> items;
+
+        public EditorItemAdapter(Context context, int textViewResourceId,
+                                 ArrayList<EditorItem> items)
+        {
+            super(context, textViewResourceId, items);
+            this.items = items;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            if (position == 0)
+                return VIEW_TYPE_SCENE_NAME;
+            else
+                return VIEW_TYPE_SCENE_OPTION;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return VIEW_TYPE_COUNT;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (position == 0)
+                return getViewScene(position, convertView);
+            else
+                return getViewOption(position, convertView);
+        }
+
+        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.benchmark_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.benchmark_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;
+        }
+    }
 }
diff --git a/android/src/org/linaro/glmark2/MainActivity.java b/android/src/org/linaro/glmark2/MainActivity.java
index a04a7ad..a5c9531 100644
--- a/android/src/org/linaro/glmark2/MainActivity.java
+++ b/android/src/org/linaro/glmark2/MainActivity.java
@@ -1,3 +1,24 @@
+/*
+ * 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;
@@ -34,6 +55,7 @@
 
     ArrayList<String> benchmarks;
     BaseAdapter adapter;
+    SceneInfo[] sceneInfoList;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -93,9 +115,9 @@
         }
     }
 
-    /** 
+    /**
      * Initialize the activity.
-     * 
+     *
      * @param savedBenchmarks a list of benchmarks to load the list with (or null)
      */
     private void init(ArrayList<String> savedBenchmarks)
@@ -109,6 +131,9 @@
             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() {
@@ -128,7 +153,7 @@
         adapter = new BenchmarkAdapter(this, R.layout.benchmark_item, benchmarks);
         lv.setAdapter(adapter);
 
-        lv.setOnItemClickListener(new OnItemClickListener() {  
+        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);
@@ -136,6 +161,7 @@
                     t = "";
                 intent.putExtra("benchmark-text", t);
                 intent.putExtra("benchmark-pos", position);
+                intent.putExtra("scene-info", sceneInfoList);
                 startActivityForResult(intent, 1);
             }
         });
@@ -153,9 +179,9 @@
 
     }
 
-    /** 
+    /**
      * 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
@@ -220,7 +246,7 @@
         });
     }
 
-    /** 
+    /**
      * A ListView adapter that creates item views from benchmark strings.
      */
     private class BenchmarkAdapter extends ArrayAdapter<String> {
@@ -258,4 +284,8 @@
             return v;
         }
     }
+
+    static {
+        System.loadLibrary("glmark2-android");
+    }
 }
diff --git a/android/src/org/linaro/glmark2/SceneInfo.java b/android/src/org/linaro/glmark2/SceneInfo.java
index a353c7a..7a85e07 100644
--- a/android/src/org/linaro/glmark2/SceneInfo.java
+++ b/android/src/org/linaro/glmark2/SceneInfo.java
@@ -26,7 +26,7 @@
 import java.util.ArrayList;
 
 class SceneInfo implements Parcelable {
-    class Option {
+    static class Option {
         String name;
         String description;
         String defaultValue;