Use file to pass arguments to the shell.

Passing parameters directly through the intent have the following
issues:
- It limits the size of the parameters to 1024 bytes.
- It is regularly broken by new versions of adb.

R=ppi@chromium.org
BUG=Fixes https://github.com/domokit/mojo/issues/488

Review URL: https://codereview.chromium.org/1414633004 .
diff --git a/mojo/devtools/common/devtoolslib/android_shell.py b/mojo/devtools/common/devtoolslib/android_shell.py
index 611732e..ab03bb2 100644
--- a/mojo/devtools/common/devtoolslib/android_shell.py
+++ b/mojo/devtools/common/devtoolslib/android_shell.py
@@ -15,6 +15,7 @@
 import tempfile
 import threading
 import time
+import uuid
 
 from devtoolslib.http_server import start_http_server
 from devtoolslib.shell import Shell
@@ -349,8 +350,20 @@
     parameters.extend(arguments)
 
     if parameters:
-      encodedParameters = json.dumps(parameters)
-      cmd += ['--es', 'encodedParameters', encodedParameters]
+      device_filename = (
+          '/sdcard/%s/args_%s' % (_MOJO_SHELL_PACKAGE_NAME, str(uuid.uuid4())))
+      with tempfile.NamedTemporaryFile(delete=False) as temp:
+        try:
+          for parameter in parameters:
+            temp.write(parameter)
+            temp.write('\n')
+          temp.close()
+          subprocess.check_call(self._adb_command(
+              ['push', temp.name, device_filename]))
+        finally:
+          os.remove(temp.name)
+
+      cmd += ['--es', 'argsFile', device_filename]
 
     subprocess.check_call(cmd, stdout=self.verbose_pipe)
 
diff --git a/shell/android/apk/AndroidManifest.xml.jinja2 b/shell/android/apk/AndroidManifest.xml.jinja2
index 0229050..9408fd4 100644
--- a/shell/android/apk/AndroidManifest.xml.jinja2
+++ b/shell/android/apk/AndroidManifest.xml.jinja2
@@ -12,14 +12,15 @@
     android:versionName="{{ version_name }}">
 
     <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23" />
-    <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_CONDITIONS"/>
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.NFC"/>
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.NFC"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application android:icon="@mipmap/ic_launcher"
             android:name="org.chromium.mojo.shell.MojoShellApplication"
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/ShellService.java b/shell/android/apk/src/org/chromium/mojo/shell/ShellService.java
index efb129c..c6f758c 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/ShellService.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/ShellService.java
@@ -14,7 +14,6 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
-import android.util.JsonReader;
 import android.util.Log;
 
 import org.chromium.base.ApplicationStatus;
@@ -28,9 +27,10 @@
 import org.chromium.mojom.mojo.ServiceProvider;
 import org.chromium.mojom.mojo.Shell;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileReader;
 import java.io.IOException;
-import java.io.StringReader;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -168,9 +168,9 @@
     public int onStartCommand(Intent intent, int flags, int startId) {
         // A client is starting this service; make sure the shell is initialized.
         // Note that ensureInitialized is gated by the mInitialized boolean flag. This means that
-        // only the first set of parameters will ever be taken into account.
+        // only the first set of arguments will ever be taken into account.
         // TODO(eseidel): ShellService can fail, but we're ignoring the return.
-        ensureStarted(getApplicationContext(), getParametersFromIntent(intent));
+        ensureStarted(getApplicationContext(), getArgsFromIntent(intent));
         if (intent.hasExtra(APPLICATION_URL_EXTRA)) {
             // This intent requests we start an application.
             String urlExtra = intent.getStringExtra(APPLICATION_URL_EXTRA);
@@ -200,7 +200,7 @@
     /**
      * Initializes the native system and starts the shell.
      **/
-    private void ensureStarted(Context applicationContext, String[] parameters) {
+    private void ensureStarted(Context applicationContext, String[] args) {
         if (mInitialized) return;
         try {
             FileHelper.extractFromAssets(applicationContext, NETWORK_LIBRARY_APP,
@@ -212,22 +212,21 @@
             // The shell child executable needs to be ... executable.
             mojoShellChild.setExecutable(true, true);
 
-            List<String> parametersList = new ArrayList<String>();
+            List<String> argsList = new ArrayList<String>();
 
-            parametersList.add("--args-for=mojo:notifications " + R.mipmap.ic_launcher);
+            argsList.add("--args-for=mojo:notifications " + R.mipmap.ic_launcher);
 
             // Program name.
-            if (parameters != null) {
-                parametersList.addAll(Arrays.asList(parameters));
+            if (args != null) {
+                argsList.addAll(Arrays.asList(args));
             } else {
-                // Apply default parameters.
-                parametersList.add("--origin=" + DEFAULT_ORIGIN);
-                parametersList.add("--url-mappings=mojo:window_manager=" + DEFAULT_WM);
+                // Apply default arguments.
+                argsList.add("--origin=" + DEFAULT_ORIGIN);
+                argsList.add("--url-mappings=mojo:window_manager=" + DEFAULT_WM);
             }
 
             nativeStart(applicationContext, applicationContext.getAssets(),
-                    mojoShellChild.getAbsolutePath(),
-                    parametersList.toArray(new String[parametersList.size()]),
+                    mojoShellChild.getAbsolutePath(), argsList.toArray(new String[argsList.size()]),
                     getLocalAppsDir(applicationContext).getAbsolutePath(),
                     getTmpDir(applicationContext).getAbsolutePath(),
                     getHomeDir(applicationContext).getAbsolutePath());
@@ -238,28 +237,31 @@
         }
     }
 
-    private static String[] getParametersFromIntent(Intent intent) {
+    private static String[] getArgsFromIntent(Intent intent) {
         if (intent == null) {
             return null;
         }
-        String[] parameters = intent.getStringArrayExtra("parameters");
-        if (parameters != null) {
-            return parameters;
-        }
-        String encodedParameters = intent.getStringExtra("encodedParameters");
-        if (encodedParameters != null) {
-            JsonReader reader = new JsonReader(new StringReader(encodedParameters));
-            List<String> parametersList = new ArrayList<String>();
+        String argsFile = intent.getStringExtra("argsFile");
+        if (argsFile != null) {
+            File file = new File(argsFile);
+            if (!file.isFile()) {
+                return null;
+            }
             try {
-                reader.beginArray();
-                while (reader.hasNext()) {
-                    parametersList.add(reader.nextString());
+                List<String> argsList = new ArrayList<String>();
+                try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
+                    String line;
+                    while ((line = bufferedReader.readLine()) != null) {
+                        argsList.add(line);
+                    }
+                    return argsList.toArray(new String[argsList.size()]);
+                } catch (IOException e) {
+                    Log.w(TAG, e.getMessage(), e);
                 }
-                reader.endArray();
-                reader.close();
-                return parametersList.toArray(new String[parametersList.size()]);
-            } catch (IOException e) {
-                Log.w(TAG, e.getMessage(), e);
+            } finally {
+                if (!file.delete()) {
+                    Log.w(TAG, "Unable to delete args file.");
+                }
             }
         }
         return null;
@@ -320,8 +322,8 @@
      * Initializes the native system. This API should be called only once per process.
      **/
     private static native void nativeStart(Context context, AssetManager assetManager,
-            String mojoShellChildPath, String[] parameters, String bundledAppsDirectory,
-            String tmpDir, String homeDir);
+            String mojoShellChildPath, String[] args, String bundledAppsDirectory, String tmpDir,
+            String homeDir);
 
     private static native void nativeAddApplicationURL(String url);