From 608dc5dba33f4911ad1130faa36524480273195f Mon Sep 17 00:00:00 2001
From: Christophe Geuzaine <cgeuzaine@ulg.ac.be>
Date: Fri, 7 Oct 2016 16:32:49 +0000
Subject: [PATCH] bring Android up to par with iOS: - share model files by
 email - edit model files - clear results - remove screenshot menu - new help
 menu - visible about button

---
 contrib/mobile/Android/AndroidManifest.xml    |   4 +-
 contrib/mobile/Android/res/values/strings.xml |   2 -
 .../src/org/geuz/onelab/AboutActivity.java    | 113 ++++++++++++----
 .../src/org/geuz/onelab/MainActivity.java     |  27 ----
 .../src/org/geuz/onelab/ModelList.java        | 124 +++++++++++++++---
 5 files changed, 196 insertions(+), 74 deletions(-)

diff --git a/contrib/mobile/Android/AndroidManifest.xml b/contrib/mobile/Android/AndroidManifest.xml
index 42ec5c5f5f..661cd4a7e3 100644
--- a/contrib/mobile/Android/AndroidManifest.xml
+++ b/contrib/mobile/Android/AndroidManifest.xml
@@ -1,7 +1,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="org.geuz.onelab"
-          android:versionCode="22"
-          android:versionName="1.3.1"
+          android:versionCode="23"
+          android:versionName="1.4.0"
           android:installLocation="auto" >
   
   <uses-sdk android:minSdkVersion="14"
diff --git a/contrib/mobile/Android/res/values/strings.xml b/contrib/mobile/Android/res/values/strings.xml
index 119c913106..86b0d470c5 100644
--- a/contrib/mobile/Android/res/values/strings.xml
+++ b/contrib/mobile/Android/res/values/strings.xml
@@ -3,8 +3,6 @@
   <string name="title_activity_main">Onelab</string>
   <string name="title_activity_about">About</string>
   <string name="title_activity_options">Parameters</string>
-  <string name="title_share">Share screenshot with &#8230;</string>
-  <string name="menu_share">Share screenshot</string>
   <string name="menu_parameters">Parameters</string>
   <string name="menu_run">Run</string>
   <string name="menu_stop">Stop</string>
diff --git a/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java b/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java
index 29c7d8d431..9755bd3ee8 100644
--- a/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java
+++ b/contrib/mobile/Android/src/org/geuz/onelab/AboutActivity.java
@@ -3,52 +3,117 @@ package org.geuz.onelab;
 import org.geuz.onelab.Gmsh;
 
 import java.lang.String;
+import java.io.File;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.webkit.WebView;
+import android.webkit.JavascriptInterface;
+import android.os.Bundle;
+
+class MyJavaScriptInterface {
+  private String _filename;
+  public MyJavaScriptInterface(String filename){
+    _filename = filename;
+  }
+  @JavascriptInterface
+  public void myJsCallback(String jsResult) {
+    File outputFile = new File(_filename);
+    try{
+      BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile));
+      writer.write(jsResult);
+      writer.close();
+    }
+    catch (IOException e) {
+    }
+  }
+}
 
 public class AboutActivity extends Activity{
   private MenuItem _saveMenuItem;
+  private boolean _editingMode;
+  private WebView _webview;
 
   protected void onCreate(android.os.Bundle savedInstanceState)
   {
     super.onCreate(savedInstanceState);
     getActionBar().setDisplayHomeAsUpEnabled(true);
 
-    //getActionBar().setTitle("Editing");
+    _webview = new WebView(this);
+    _webview.getSettings().setJavaScriptEnabled(true);
 
-    WebView webview = new WebView(this);
-    String aboutGmsh = Gmsh.getAboutGmsh();
-    String aboutGetDP = Gmsh.getAboutGetDP();
-    String aboutOnelab = "<p>&nbsp;</p><center><h3>Onelab/Mobile</h3>";
-    try {
-      aboutOnelab += "Version " + this.getPackageManager().
-        getPackageInfo(this.getPackageName(), 0).versionName;
+    String css = "body { background-color: #FFFFFF; color: #252525; margin: 35px 10px 35px 10px; padding: 0; font-family: helvetica,sans-serif; font-size: 1em; }";
+
+    Intent intent = getIntent();
+    Bundle extras = intent.getExtras();
+    if(extras != null) {
+      String name = extras.getString("name");
+      getActionBar().setTitle(name);
+      if(name.equals("Help")){
+        _editingMode = false;
+        _webview.loadDataWithBaseURL("", "<html><head><style type=\"text/css\">" + css + " h3 { text-align: center; } </style></head><h3>Onelab/Mobile</h3> <h4>Running an existing model</h4> <p> The list of available models appears when you launch the app. Selecting a model will load it. You can then press <b>Run</b> to launch a simulation with the default set of parameters. When available, additional information about a model can be obtained by long-pressing on the model description and selecting <b>Visit model website</b>.</p> <h4>Modifying a model</h4> <p>To run a model with different parameters, press <b>Parameters</b> and modify any of the presets. Then press <b>Run</b> again: all the simulation steps will be performed with the new parameter values. To restore the preset parameters values, press <b>Reset</b>. </p> <p> Advanced users can also directly edit the model input files: long-press on the model description and select <b>Edit model files</b>. </p> <p> To free up space, temporary model files (meshes, solution files) can be removed by long-pressing on the model description and selecting <b>Clear results</b>. </p> <p> To completey remove a model, long-press on the model description and select <b>Remove</b>. </p> <h4>Sharing a model</h4> <p> To share a model by email, long-press on the model description and select <b>Email model files</b>. </p> <h4>Installing a new model</h4> <p> To install a new model: <ol> <li>Put all the model files (.pro, .geo) in a directory, which should also contain a file named <code>infos.xml</code> with the model information: <pre>\n&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;models&gt;\n  &lt;model&gt;\n    &lt;title&gt;Model title&lt;/title&gt;\n    &lt;summary&gt;Model summary&lt;/summary&gt;\n    &lt;file type=\"pro\"&gt;main_pro_file.pro&lt;/file&gt;\n    &lt;preview type=\"png\"&gt;128x128_pixel_screenshot.png&lt;/preview&gt;\n    &lt;url&gt;http://model_website.com&lt;/url&gt;\n  &lt;/model&gt;\n&lt;/models&gt;\n</pre><li>Zip the directory. <li>Open the .zip file on your device, e.g. by copying it on an SD card, through Google Drive or by  emailing it to yourself and opening the attachment; or by putting it  on a web server and downloading the file on the device with Chrome. <li>Refresh the list of models: the new model will be extracted  alongside Onelab/Mobile\'s built-in models.</ol> <p style=\"padding-top: 2em;\">Visit <a href=\"http://onelab.info/\">http://onelab.info/</a> for more information.</p> </body></html>", "text/html", "UTF-8", "");
+      }
+      else{
+        _editingMode = true;
+        String filename = extras.getString("file");
+        MyJavaScriptInterface javaInterface = new MyJavaScriptInterface(filename);
+        _webview.addJavascriptInterface(javaInterface, "HTMLOUT");
+        File file = new File(filename);
+        StringBuilder text = new StringBuilder();
+        try {
+          BufferedReader br = new BufferedReader(new FileReader(file));
+          String line;
+          while ((line = br.readLine()) != null) {
+            text.append(line);
+            text.append('\n');
+          }
+          br.close();
+        }
+        catch (IOException e) {
+        }
+        String code = text.toString().replace("<", "&lt;");
+        code = code.toString().replace(">", "&gt;");
+        String js = "!function(a,b){\"function\"==typeof define&&define.amd?define([\"exports\"],b):b(\"undefined\"!=typeof exports?exports:a.microlight={})}(this,function(a){var k,l,m,b=window,c=document,d=\"appendChild\",e=\"test\",g=\"opacity:.\",n=function(a){for(l=c.getElementsByClassName(a||\"microlight\"),k=0;m=l[k++];){var n,o,r,s,t,f=m.textContent,h=0,i=f[0],j=1,p=m.innerHTML=\"\",q=0,u=/(\\d*\\, \\d*\\, \\d*)(, ([.\\d]*))?/g.exec(b.getComputedStyle(m).color);for(\"px rgba(\"+u[1]+\",\",u[3]||1;o=n,n=q<7&&\"\\\\\"==n?1:j;){if(j=i,i=f[++h],s=p.length>1,!j||q>8&&\"\\n\"==j||[/\\S/[e](j),1,1,!/[$\\w]/[e](j),(\"/\"==n||\"\\n\"==n)&&s,\'\"\'==n&&s,\"\'\"==n&&s,f[h-4]+o+n==\"-->\",o+n==\"*/\"][q])for(p&&(m[d](t=c.createElement(\"span\")).setAttribute(\"style\",[\"\",\"font-weight:bold;color:#00c;\",g+6,\"color: #a08;\"+g+8,\"font-style:italic;color: #b00;\"+g+8][q?q<3?2:q>6?4:q>3?3:+/^(If|Else|ElseIf|EndIf|Include|For|EndFor|Include|Macro|Return)$/[e](p):0]),t[d](c.createTextNode(p))),r=q&&q<7?q:r,p=\"\",q=11;![1,/[\\/{}[(\\-+*=<>:;|\\\\.,?!&@~]/[e](j),/[\\])]/[e](j),/[$\\w]/[e](j),\"/\"==j&&r<2&&\"<\"!=n,\'\"\'==j,\"\'\"==j,j+i+f[h+1]+f[h+2]==\"<!--\",j+i==\"/*\",j+i==\"//\",j+i==\"//\"][--q];);p+=j}}};a.reset=n,\"complete\"==c.readyState?n():b.addEventListener(\"load\",function(){n()},0)});";
+        _webview.loadDataWithBaseURL("", "<html><head><script>" + js +
+                                    "</script></head><body><pre contenteditable=\"true\" class=microlight>" +
+                                    code + "</pre></body></html>", "text/html", "UTF-8", "");
+      }
     }
-    catch (android.content.pm.PackageManager.NameNotFoundException e) {
-      aboutOnelab += "Version ?.?.?";
+    else{
+      _editingMode = false;
+      String aboutOnelab = "<center><h3>Onelab/Mobile</h3>";
+      try {
+        aboutOnelab += "Version " + this.getPackageManager().
+          getPackageInfo(this.getPackageName(), 0).versionName;
+      }
+      catch (android.content.pm.PackageManager.NameNotFoundException e) {
+      }
+      aboutOnelab += "<p>Copyright (C) 2014-2016 Christophe Geuzaine and Maxime Graulich, ";
+      aboutOnelab += "University of Li&egrave;ge</p>";
+      aboutOnelab += "<p>Visit <a href=\"http://onelab.info/\">http://onelab.info/</a> ";
+      aboutOnelab += "for more information</p>";
+      aboutOnelab += "<p>&nbsp;</p><p>This version of Onelab/Mobile contains:</p></center>";
+      _webview.loadDataWithBaseURL("", "<html><head><style type=\"text/css\">" + css + "</style></head><body>" +
+                                  aboutOnelab + Gmsh.getAboutGmsh() + Gmsh.getAboutGetDP() + "</body></html>",
+                                  "text/html", "UTF-8", "");
     }
-
-    //aboutOnelab += "<pre contenteditable=\"true\"> THIS IS EDITABLE </pre>";
-
-    aboutOnelab += "<p>Copyright (C) 2014-2016 Christophe Geuzaine and Maxime Graulich, ";
-    aboutOnelab += "University of Li&egrave;ge</p>";
-    aboutOnelab += "<p>Visit <a href=\"http://onelab.info/\">http://onelab.info/</a> ";
-    aboutOnelab += "for more information</p>";
-    aboutOnelab += "<p>&nbsp;</p><p>This version of Onelab/Mobile contains:</p>";
-    webview.loadDataWithBaseURL("", aboutOnelab + aboutGmsh + aboutGetDP,
-                                "text/html", "UTF-8", "");
-    setContentView(webview);
+    setContentView(_webview);
   }
   @Override
   public boolean onCreateOptionsMenu(Menu menu)
   {
     super.onCreateOptionsMenu(menu);
-    //_saveMenuItem = menu.add(R.string.menu_save);
-    //_saveMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+    if(_editingMode){
+      _saveMenuItem = menu.add(R.string.menu_save);
+      _saveMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+    }
     return true;
   }
   @Override
@@ -60,7 +125,7 @@ public class AboutActivity extends Activity{
       this.finish();
     }
     else if(item.getTitle().equals(getString(R.string.menu_save))){
-
+      _webview.loadUrl("javascript:( function () { window.HTMLOUT.myJsCallback(document.body.innerText); } ) ()");
     }
     return super.onMenuItemSelected(featureId, item);
   }
diff --git a/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java b/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java
index 135d288475..dce8e2004d 100644
--- a/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java
+++ b/contrib/mobile/Android/src/org/geuz/onelab/MainActivity.java
@@ -105,8 +105,6 @@ public class MainActivity extends Activity{
     }
     _runStopMenuItem = menu.add((_compute)?R.string.menu_stop:R.string.menu_run);
     _runStopMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-    MenuItem shareMenuItem = menu.add(R.string.menu_share);
-    shareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
     return true;
   }
 
@@ -135,31 +133,6 @@ public class MainActivity extends Activity{
       _runStopMenuItem.setEnabled(false);
       _gmsh.onelabCB("stop");
     }
-    else if(item.getTitle().equals(getString(R.string.menu_share))) {
-      if(this._compute) {
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
-        _errorDialog = dialogBuilder.setTitle("Cannot show the model list")
-          .setMessage("The computation has to complete before you can take a screenshot")
-          .setPositiveButton("OK", new DialogInterface.OnClickListener() {
-              public void onClick(DialogInterface dialog, int which) {
-                dialog.dismiss();
-              }
-            })
-          .show();
-      }
-      else {
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
-        File file = new File(this.getExternalFilesDir(null),
-                             "onelab-screenshot-"+dateFormat.format(new Date())+".png");
-        file.setReadable(true, false);
-        _modelFragment.takeScreenshot(file);
-        Intent shareIntent = new Intent();
-        shareIntent.setAction(Intent.ACTION_SEND);
-        shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
-        shareIntent.setType("image/jpeg");
-        startActivity(Intent.createChooser(shareIntent, getString(R.string.title_share)));
-      }
-    }
     else if(item.getItemId() == android.R.id.home) {
       if(this._compute) {
         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
diff --git a/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java b/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java
index 8dbfc3b09f..b5254f18d2 100644
--- a/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java
+++ b/contrib/mobile/Android/src/org/geuz/onelab/ModelList.java
@@ -2,8 +2,10 @@ package org.geuz.onelab;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -27,6 +29,7 @@ import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.Toast;
 import android.util.Log;
+import android.os.Environment;
 
 public class ModelList extends Activity {
   private ModelArrayAdapter _modelArrayAdapter;
@@ -40,6 +43,18 @@ public class ModelList extends Activity {
     fileOrDirectory.delete();
   }
 
+  private void copyFile(File src, File dst) throws IOException {
+    FileInputStream in = new FileInputStream(src);
+    FileOutputStream out = new FileOutputStream(dst);
+    byte[] buf = new byte[1024];
+    int len;
+    while ((len = in.read(buf)) > 0) {
+      out.write(buf, 0, len);
+    }
+    in.close();
+    out.close();
+  }
+
   @Override
   protected void onCreate(Bundle savedInstanceState)
   {
@@ -74,17 +89,21 @@ public class ModelList extends Activity {
           final Model m = _modelArrayAdapter.getModel(position);
           CharSequence[] actions;
           if(m.getUrl() != null) {
-            actions = new CharSequence[4];
+            actions = new CharSequence[6];
             actions[0] = "Open";
             actions[1] = "Remove";
-            actions[2] = "Edit model files";
-            actions[3] = "Visit model website";
+            actions[2] = "Clear results";
+            actions[3] = "Edit model files";
+            actions[4] = "Email model files";
+            actions[5] = "Visit model website";
           }
           else {
-            actions = new CharSequence[3];
+            actions = new CharSequence[5];
             actions[0] = "Open";
             actions[1] = "Remove";
-            actions[2] = "Edit model files";
+            actions[2] = "Clear results";
+            actions[3] = "Edit model files";
+            actions[4] = "Email model files";
           }
           final AdapterView<?> p = parent;
           AlertDialog.Builder builder = new AlertDialog.Builder(p.getContext());
@@ -112,26 +131,86 @@ public class ModelList extends Activity {
                   }
                   break;
                 case 2:
-                  {/*
+                  {
+                    File folder = m.getFile().getParentFile();
+                    File[] files = folder.listFiles();
+                    for (int i = 0; i < files.length; i++) {
+                      if (files[i].isFile()) {
+                        String filenameArray[] = files[i].getName().split("\\.");
+                        String extension = filenameArray[filenameArray.length-1];
+                        if(extension.equalsIgnoreCase("msh")  ||
+                           extension.equalsIgnoreCase("pre")  ||
+                           extension.equalsIgnoreCase("res")  ||
+                           extension.equalsIgnoreCase("pos")){
+                          deleteRecursive(files[i]);
+                        }
+                      }
+                    }
+                  }
+                  break;
+                case 3:
+                  {
                     AlertDialog.Builder builder = new AlertDialog.Builder(p.getContext());
                     builder.setTitle("Edit model files");
-                    CharSequence[] act;
-                    act = new CharSequence[1];
-                    act[0] = "Test";
-                    builder.setItems(act, new DialogInterface.OnClickListener() {
+                    File folder = m.getFile().getParentFile();
+                    File[] files = folder.listFiles();
+                    ArrayList<String> selection = new ArrayList<String>();
+                    for (int i = 0; i < files.length; i++) {
+                      if (files[i].isFile()) {
+                        String filenameArray[] = files[i].getName().split("\\.");
+                        String extension = filenameArray[filenameArray.length-1];
+                        if(extension.equalsIgnoreCase("txt")  ||
+                           extension.equalsIgnoreCase("geo")  ||
+                           extension.equalsIgnoreCase("pro")  ||
+                           extension.equalsIgnoreCase("dat")){
+                          selection.add(files[i].getName());
+                        }
+                      }
+                    }
+                    final String[] names = new String[selection.size()];
+                    for (int i = 0; i < selection.size(); i++)
+                      names[i] = selection.get(i);
+                    builder.setItems(names, new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int position) {
-                        // Intent intent = new Intent(ModelList.this, AboutActivity.class);
-                        // intent.putExtra("file", m.getFile().toString());
-                        // intent.putExtra("name", m.getName());
-                        // startActivity(intent);
+                          Intent intent = new Intent(ModelList.this, AboutActivity.class);
+                          File folder = m.getFile().getParentFile();
+                          String file = folder + "/" + names[position];
+                          intent.putExtra("file", file);
+                          intent.putExtra("name", names[position]);
+                          startActivity(intent);
                         }
                       });
                     AlertDialog alert = builder.create();
                     alert.show();
-                   */
                   }
                   break;
-                case 3:
+                case 4:
+                  {
+                    Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+                    emailIntent.setType("vnd.android.cursor.dir/email");
+                    File folder = m.getFile().getParentFile();
+                    File[] files = folder.listFiles();
+                    emailIntent.putExtra(Intent.EXTRA_SUBJECT, folder+"/"+files[0].getName());
+                    ArrayList<Uri> copies = new ArrayList<Uri>();
+                    for (int i = 0; i < files.length; i++) {
+                      if (files[i].isFile()) {
+                        // need to copy as we cannot attach files directly from
+                        // the app data dir
+                        File copy = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),
+                                             files[i].getName());
+                        try{
+                          copyFile(files[i], copy);
+                        }
+                        catch (IOException e) {
+                        }
+                        copies.add(Uri.fromFile(copy));
+                      }
+                    }
+                    emailIntent.putExtra(Intent.EXTRA_STREAM, copies);
+                    startActivity(Intent.createChooser(emailIntent , "Share model files..."));
+                  }
+                  break;
+                case 5:
                   {
                     Intent browserIntent = new Intent(Intent.ACTION_VIEW, m.getUrl());
                     startActivity(browserIntent);
@@ -154,9 +233,10 @@ public class ModelList extends Activity {
   @Override
   public boolean onCreateOptionsMenu(Menu menu)
   {
+    MenuItem help = menu.add("Help");
+    help.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
     MenuItem about = menu.add("About");
-    about.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-
+    about.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
     return super.onCreateOptionsMenu(menu);
   }
 
@@ -167,6 +247,12 @@ public class ModelList extends Activity {
       Intent intent = new Intent(ModelList.this, AboutActivity.class);
       startActivity(intent);
     }
+    else if(item.getTitle().equals("Help")) {
+      Intent intent = new Intent(ModelList.this, AboutActivity.class);
+      intent.putExtra("file", "Help");
+      intent.putExtra("name", "Help");
+      startActivity(intent);
+    }
     return super.onMenuItemSelected(featureId, item);
   }
 
@@ -194,7 +280,7 @@ public class ModelList extends Activity {
   {
     File document = this.getFilesDir();
     File files[] = document.listFiles();
-    for(int i=0; i<files.length; i++) {
+    for(int i = 0; i < files.length; i++) {
       if(files[i].isDirectory()) { // models are in directory
         File xmlInfos = new File(files[i], "infos.xml");
         if(!xmlInfos.isFile()) continue;
-- 
GitLab