summaryrefslogtreecommitdiff
path: root/java/android/controller/src
diff options
context:
space:
mode:
authorMark Spruiell <mes@zeroc.com>2017-06-06 15:51:30 -0700
committerMark Spruiell <mes@zeroc.com>2017-06-06 15:51:30 -0700
commite99e1388d3b45e2e93888bca29ed540f245e2166 (patch)
tree8685d4a3a0ca84f257e2b326965609da554a60ba /java/android/controller/src
parentFixed Ice/operations build failure with ARC target of iOS controller project (diff)
downloadice-e99e1388d3b45e2e93888bca29ed540f245e2166.tar.bz2
ice-e99e1388d3b45e2e93888bca29ed540f245e2166.tar.xz
ice-e99e1388d3b45e2e93888bca29ed540f245e2166.zip
ICE-7903 - move android tests
Diffstat (limited to 'java/android/controller/src')
-rw-r--r--java/android/controller/src/main/AndroidManifest.xml23
-rw-r--r--java/android/controller/src/main/assets/.gitignore1
-rw-r--r--java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerActivity.java87
-rw-r--r--java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerApp.java542
-rw-r--r--java/android/controller/src/main/res/layout/main.xml55
-rw-r--r--java/android/controller/src/main/res/raw/icon.pngbin0 -> 3180 bytes
-rw-r--r--java/android/controller/src/main/res/values/strings.xml4
7 files changed, 712 insertions, 0 deletions
diff --git a/java/android/controller/src/main/AndroidManifest.xml b/java/android/controller/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..e1e041d8acd
--- /dev/null
+++ b/java/android/controller/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.zeroc.testcontroller"
+ android:versionCode="1"
+ android:versionName="1.0.0">
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+
+ <!--
+ http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
+ -->
+ <application android:icon="@raw/icon" android:label="@string/app_name" android:name="ControllerApp"
+ android:allowBackup="false">
+ <activity android:name="ControllerActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+</application>
+</manifest>
diff --git a/java/android/controller/src/main/assets/.gitignore b/java/android/controller/src/main/assets/.gitignore
new file mode 100644
index 00000000000..2cbf86bc394
--- /dev/null
+++ b/java/android/controller/src/main/assets/.gitignore
@@ -0,0 +1 @@
+*.dex
diff --git a/java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerActivity.java b/java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerActivity.java
new file mode 100644
index 00000000000..e7001f6f156
--- /dev/null
+++ b/java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerActivity.java
@@ -0,0 +1,87 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package com.zeroc.testcontroller;
+
+import java.util.LinkedList;
+import android.app.*;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.widget.*;
+import android.view.View;
+
+public class ControllerActivity extends ListActivity
+{
+ private WifiManager _wifiManager;
+ private WifiManager.MulticastLock _lock;
+ private LinkedList<String> _output = new LinkedList<String>();
+ private ArrayAdapter<String> _outputAdapter;
+ private ArrayAdapter<String> _ipv4Adapter;
+ private ArrayAdapter<String> _ipv6Adapter;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ _wifiManager = (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ _lock = _wifiManager.createMulticastLock("com.zeroc.testcontroller");
+ _lock.acquire();
+
+ _outputAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, _output);
+ setListAdapter(_outputAdapter);
+ final ControllerApp app = (ControllerApp)getApplication();
+ final java.util.List<String> ipv4Addresses = app.getAddresses(false);
+ _ipv4Adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, ipv4Addresses);
+ Spinner s = (Spinner)findViewById(R.id.ipv4);
+ s.setAdapter(_ipv4Adapter);
+ s.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener()
+ {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
+ {
+ app.setIpv4Address(ipv4Addresses.get((int)id));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> arg0)
+ {
+ }
+ });
+ s.setSelection(0);
+
+ final java.util.List<String> ipv6Addresses = app.getAddresses(true);
+ _ipv6Adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, ipv6Addresses);
+ s = (Spinner)findViewById(R.id.ipv6);
+ s.setAdapter(_ipv6Adapter);
+ s.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener()
+ {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
+ {
+ app.setIpv6Address(ipv6Addresses.get((int)id));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> arg0)
+ {
+ }
+ });
+ s.setSelection(0);
+ app.startController(this);
+ }
+
+ public synchronized void println(String data)
+ {
+ _output.add(data);
+ _outputAdapter.notifyDataSetChanged();
+ }
+}
diff --git a/java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerApp.java b/java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerApp.java
new file mode 100644
index 00000000000..3c43d86416b
--- /dev/null
+++ b/java/android/controller/src/main/java/com/zeroc/testcontroller/ControllerApp.java
@@ -0,0 +1,542 @@
+// **********************************************************************
+//
+// Copyright (c) 2003-2017 ZeroC, Inc. All rights reserved.
+//
+// This copy of Ice is licensed to you under the terms described in the
+// ICE_LICENSE file included in this distribution.
+//
+// **********************************************************************
+
+package com.zeroc.testcontroller;
+
+import java.io.*;
+import java.util.*;
+
+import com.zeroc.Ice.Logger;
+import com.zeroc.Ice.Communicator;
+import com.zeroc.IceInternal.Time;
+
+import android.os.Build;
+import android.util.Log;
+import android.app.Application;
+
+import Test.Common.ProcessControllerRegistryPrx;
+import Test.Common.ProcessControllerPrx;
+import test.Util.Application.CommunicatorListener;
+
+public class ControllerApp extends Application
+{
+ private final String TAG = "ControllerApp";
+ private ControllerHelper _helper;
+ private ControllerActivity _controller;
+ private String _ipv4Address;
+ private String _ipv6Address;
+
+ static private class TestSuiteBundle
+ {
+ @SuppressWarnings("unchecked")
+ TestSuiteBundle(String name, ClassLoader loader) throws ClassNotFoundException
+ {
+ _loader = loader;
+ _class = (Class<? extends test.Util.Application>)_loader.loadClass(name);
+ }
+
+ test.Util.Application newInstance()
+ throws IllegalAccessException, InstantiationException
+ {
+ if(_class == null)
+ {
+ return null;
+ }
+ return _class.newInstance();
+ }
+
+ ClassLoader getClassLoader()
+ {
+ return _loader;
+ }
+
+ private String _name;
+ private ClassLoader _loader;
+ private Class<? extends test.Util.Application> _class;
+ }
+
+ class AndroidLogger implements Logger
+ {
+ private final String _prefix;
+
+ AndroidLogger(String prefix)
+ {
+ _prefix = prefix;
+ }
+
+ @Override
+ public void print(String message)
+ {
+ Log.d(TAG, message);
+ }
+
+ @Override
+ public void trace(String category, String message)
+ {
+ Log.v(category, message);
+ }
+
+ @Override
+ public void warning(String message)
+ {
+ Log.w(TAG, message);
+ }
+
+ @Override
+ public void error(String message)
+ {
+ Log.e(TAG, message);
+ }
+
+ @Override
+ public String getPrefix()
+ {
+ return _prefix;
+ }
+
+ @Override
+ public Logger cloneWithPrefix(String s)
+ {
+ return new AndroidLogger(s);
+ }
+ }
+
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+ com.zeroc.Ice.Util.setProcessLogger(new AndroidLogger(""));
+ }
+
+ synchronized public void setIpv4Address(String address)
+ {
+ _ipv4Address = address;
+ }
+
+ synchronized public void setIpv6Address(String address)
+ {
+ int i = address.indexOf("%");
+ _ipv6Address = i == -1 ? address : address.substring(i);
+ }
+
+ public List<String> getAddresses(boolean ipv6)
+ {
+ List<String> addresses = new java.util.ArrayList<String>();
+ try
+ {
+ java.util.Enumeration<java.net.NetworkInterface> ifaces = java.net.NetworkInterface.getNetworkInterfaces();
+ while(ifaces.hasMoreElements())
+ {
+ java.net.NetworkInterface iface = ifaces.nextElement();
+ java.util.Enumeration<java.net.InetAddress> addrs = iface.getInetAddresses();
+ while (addrs.hasMoreElements())
+ {
+ java.net.InetAddress addr = addrs.nextElement();
+ if((ipv6 && addr instanceof java.net.Inet6Address) ||
+ (!ipv6 && !(addr instanceof java.net.Inet6Address)))
+ {
+ addresses.add(addr.getHostAddress());
+ }
+ }
+ }
+ }
+ catch(java.net.SocketException ex)
+ {
+ }
+ return addresses;
+ }
+
+ public synchronized void startController(ControllerActivity controller)
+ {
+ if(_helper == null)
+ {
+ _controller = controller;
+ _helper = new ControllerHelper();
+ }
+ else
+ {
+ _controller = controller;
+ }
+ }
+
+ public synchronized void println(final String data)
+ {
+ _controller.runOnUiThread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ synchronized(ControllerApp.this)
+ {
+ _controller.println(data);
+ }
+ }
+ });
+ }
+
+ public static boolean isEmulator()
+ {
+ return Build.FINGERPRINT.startsWith("generic") ||
+ Build.FINGERPRINT.startsWith("unknown") ||
+ Build.MODEL.contains("google_sdk") ||
+ Build.MODEL.contains("Emulator") ||
+ Build.MODEL.contains("Android SDK built for x86") ||
+ Build.MANUFACTURER.contains("Genymotion") ||
+ (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) ||
+ Build.PRODUCT.equals("google_sdk");
+ }
+
+ class ControllerHelper
+ {
+ public ControllerHelper()
+ {
+ com.zeroc.Ice.InitializationData initData = new com.zeroc.Ice.InitializationData();
+ initData.properties = com.zeroc.Ice.Util.createProperties();
+ initData.properties.setProperty("Ice.ThreadPool.Server.SizeMax", "10");
+ initData.properties.setProperty("ControllerAdapter.Endpoints", "tcp");
+ initData.properties.setProperty("Ice.Trace.Network", "3");
+ initData.properties.setProperty("Ice.Trace.Protocol", "1");
+ initData.properties.setProperty("ControllerAdapter.AdapterId", java.util.UUID.randomUUID().toString());
+ initData.properties.setProperty("Ice.Override.ConnectTimeout", "1000");
+ if(!isEmulator())
+ {
+ initData.properties.setProperty("Ice.Plugin.IceDiscovery", "IceDiscovery.PluginFactory");
+ initData.properties.setProperty("IceDiscovery.DomainId", "TestController");
+ }
+ _communicator = com.zeroc.Ice.Util.initialize(initData);
+ com.zeroc.Ice.ObjectAdapter adapter = _communicator.createObjectAdapter("ControllerAdapter");
+ ProcessControllerPrx processController = ProcessControllerPrx.uncheckedCast(
+ adapter.add(new ProcessControllerI(),
+ com.zeroc.Ice.Util.stringToIdentity("Android/ProcessController")));
+ adapter.activate();
+ if(isEmulator())
+ {
+ ProcessControllerRegistryPrx registry = ProcessControllerRegistryPrx.uncheckedCast(
+ _communicator.stringToProxy("Util/ProcessControllerRegistry:tcp -h 10.0.2.2 -p 15001"));
+ registerProcessController(adapter, registry, processController);
+ }
+ println("Android/ProcessController");
+ }
+
+ public void
+ registerProcessController(final com.zeroc.Ice.ObjectAdapter adapter,
+ final ProcessControllerRegistryPrx registry,
+ final ProcessControllerPrx processController)
+ {
+ registry.ice_pingAsync().whenCompleteAsync(
+ (r1, e1) ->
+ {
+ if(e1 != null)
+ {
+ handleException(e1, adapter, registry, processController);
+ }
+ else
+ {
+ com.zeroc.Ice.Connection connection = registry.ice_getConnection();
+ connection.setAdapter(adapter);
+ connection.setACM(OptionalInt.of(5),
+ Optional.of(com.zeroc.Ice.ACMClose.CloseOff),
+ Optional.of(com.zeroc.Ice.ACMHeartbeat.HeartbeatAlways));
+ connection.setCloseCallback(
+ con ->
+ {
+ println("connection with process controller registry closed");
+ while (true) {
+ try
+ {
+ Thread.sleep(500);
+ break;
+ }
+ catch(InterruptedException e)
+ {
+ }
+ }
+ registerProcessController(adapter, registry, processController);
+ });
+
+ registry.setProcessControllerAsync(processController).whenCompleteAsync(
+ (r2, e2) ->
+ {
+ if(e2 != null)
+ {
+ handleException(e2, adapter, registry, processController);
+ }
+ });
+ }
+ });
+ }
+
+ public void handleException(Throwable ex,
+ final com.zeroc.Ice.ObjectAdapter adapter,
+ final ProcessControllerRegistryPrx registry,
+ final ProcessControllerPrx processController)
+ {
+ if(ex instanceof com.zeroc.Ice.ConnectFailedException || ex instanceof com.zeroc.Ice.TimeoutException)
+ {
+ while(true)
+ {
+ try
+ {
+ Thread.sleep(500);
+ break;
+ }
+ catch(InterruptedException e)
+ {
+ }
+ }
+ registerProcessController(adapter, registry, processController);
+ }
+ else
+ {
+ println(ex.toString());
+ }
+ }
+
+ public void destroy()
+ {
+ _communicator.destroy();
+ }
+
+ private ProcessControllerRegistryPrx _registry;
+ private com.zeroc.Ice.Communicator _communicator;
+ }
+
+ class MainHelperI extends Thread implements test.Util.Application.ServerReadyListener
+ {
+ public MainHelperI(TestSuiteBundle bundle, String[] args, String exe)
+ {
+ _bundle = bundle;
+ _args = args;
+ }
+
+ public void run()
+ {
+ try
+ {
+ _app = _bundle.newInstance();
+ _app.setClassLoader(_bundle.getClassLoader());
+ _app.setCommunicatorListener(new CommunicatorListener()
+ {
+ public void communicatorInitialized(Communicator communicator)
+ {
+ if(communicator.getProperties().getProperty("Ice.Plugin.IceSSL").equals("com.zeroc.IceSSL.PluginFactory"))
+ {
+ com.zeroc.IceSSL.Plugin plugin = (com.zeroc.IceSSL.Plugin)communicator.getPluginManager().getPlugin("IceSSL");
+ String keystore = communicator.getProperties().getProperty("IceSSL.Keystore");
+ communicator.getProperties().setProperty("IceSSL.Keystore", "");
+ java.io.InputStream certs = getResources().openRawResource(
+ keystore.equals("client.bks") ? R.raw.client : R.raw.server);
+ plugin.setKeystoreStream(certs);
+ plugin.setTruststoreStream(certs);
+ communicator.getPluginManager().initializePlugins();
+ }
+ }
+ });
+ _app.setWriter(new Writer()
+ {
+ @Override
+ public void close() throws IOException
+ {
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ }
+
+ @Override
+ public void write(char[] buf, int offset, int count)
+ throws IOException
+ {
+ _out.append(buf, offset, count);
+ }
+ });
+ _app.setServerReadyListener(this);
+
+ int status = _app.main(_exe, _args);
+ synchronized(this)
+ {
+ _status = status;
+ _completed = true;
+ notifyAll();
+ }
+ }
+ catch(Exception ex)
+ {
+ _out.append(ex.toString());
+ synchronized(this)
+ {
+ _status = -1;
+ _completed = true;
+ notifyAll();
+ }
+ }
+ }
+
+ public void shutdown()
+ {
+ if(_app != null)
+ {
+ _app.stop();
+ }
+ }
+
+ public String getOutput()
+ {
+ return _out.toString();
+ }
+
+ synchronized private void completed(int status)
+ {
+ _completed = true;
+ _status = status;
+ notifyAll();
+ }
+
+ synchronized public void serverReady()
+ {
+ _ready = true;
+ notifyAll();
+ }
+
+ synchronized private void waitReady(int timeout)
+ throws Test.Common.ProcessFailedException
+ {
+ long now = Time.currentMonotonicTimeMillis();
+ while(!_ready && !_completed)
+ {
+ try
+ {
+ wait(timeout * 1000);
+ if(Time.currentMonotonicTimeMillis() - now > timeout * 1000)
+ {
+ throw new Test.Common.ProcessFailedException("timed out waiting for the process to be ready");
+ }
+ }
+ catch(java.lang.InterruptedException ex)
+ {
+ }
+ }
+
+ if(_completed && _status != 0)
+ {
+ throw new Test.Common.ProcessFailedException(_out.toString());
+ }
+ }
+
+ synchronized private int waitSuccess(int timeout)
+ throws Test.Common.ProcessFailedException
+ {
+ long now = Time.currentMonotonicTimeMillis();
+ while(!_completed)
+ {
+ try
+ {
+ wait(timeout * 1000);
+ if(Time.currentMonotonicTimeMillis() - now > timeout * 1000)
+ {
+ throw new Test.Common.ProcessFailedException("timed out waiting for the process to be ready");
+ }
+ }
+ catch(java.lang.InterruptedException ex)
+ {
+ }
+ }
+ return _status;
+ }
+
+ private TestSuiteBundle _bundle;
+ private String[] _args;
+ private String _exe;
+ private test.Util.Application _app;
+ private boolean _ready = false;
+ private boolean _completed = false;
+ private int _status = 0;
+ private final StringBuffer _out = new StringBuffer();
+ }
+
+ class ProcessControllerI implements Test.Common.ProcessController
+ {
+ public Test.Common.ProcessPrx start(final String testsuite, final String exe, String[] args,
+ com.zeroc.Ice.Current current)
+ throws Test.Common.ProcessFailedException
+ {
+ println("starting " + testsuite + " " + exe + "... ");
+ String className = "test." + testsuite.replace("/", ".") + "." + exe.substring(0, 1).toUpperCase() + exe.substring(1);
+ String dexFile = "test_" + testsuite.replace("/", "_") + ".dex";
+ try
+ {
+ TestSuiteBundle bundle = new TestSuiteBundle(className, getClassLoader());
+ MainHelperI mainHelper = new MainHelperI(bundle, args, exe);
+ mainHelper.start();
+ return Test.Common.ProcessPrx.uncheckedCast(current.adapter.addWithUUID(new ProcessI(mainHelper)));
+ }
+ catch(ClassNotFoundException ex)
+ {
+ throw new Test.Common.ProcessFailedException(
+ "testsuite `" + testsuite + "' exe ` " + exe + "' start failed:\n" + ex.toString());
+ }
+ }
+
+ public String getHost(String protocol, boolean ipv6, com.zeroc.Ice.Current current)
+ {
+ if(isEmulator())
+ {
+ return "127.0.0.1";
+ }
+ else
+ {
+ synchronized(ControllerApp.this)
+ {
+ return ipv6 ? _ipv6Address : _ipv4Address;
+ }
+ }
+ }
+ }
+
+ class ProcessI implements Test.Common.Process
+ {
+ public ProcessI(MainHelperI helper)
+ {
+ _helper = helper;
+ }
+
+ public void waitReady(int timeout, com.zeroc.Ice.Current current)
+ throws Test.Common.ProcessFailedException
+ {
+ _helper.waitReady(timeout);
+ }
+
+ public int waitSuccess(int timeout, com.zeroc.Ice.Current current)
+ throws Test.Common.ProcessFailedException
+ {
+ return _helper.waitSuccess(timeout);
+ }
+
+ public String terminate(com.zeroc.Ice.Current current)
+ {
+ _helper.shutdown();
+ current.adapter.remove(current.id);
+ while(true)
+ {
+ try
+ {
+ _helper.join();
+ break;
+ }
+ catch(InterruptedException ex)
+ {
+ }
+ }
+ return _helper.getOutput();
+ }
+
+ private MainHelperI _helper;
+ }
+}
diff --git a/java/android/controller/src/main/res/layout/main.xml b/java/android/controller/src/main/res/layout/main.xml
new file mode 100644
index 00000000000..df4c8db98c7
--- /dev/null
+++ b/java/android/controller/src/main/res/layout/main.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="5dip">
+ <TextView
+ android:layout_width="5dip"
+ android:layout_weight="25"
+ android:layout_height="wrap_content"
+ android:text="IPv4"
+ android:layout_gravity="center_vertical"/>
+
+ <Spinner
+ android:id="@+id/ipv4"
+ android:layout_width="5dip"
+ android:layout_weight="75"
+ android:layout_height="wrap_content"
+ android:drawSelectorOnTop="false"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="5dip">
+ <TextView
+ android:layout_width="0dip"
+ android:layout_weight="25"
+ android:layout_height="wrap_content"
+ android:text="IPv6"
+ android:layout_gravity="center_vertical"/>
+
+ <Spinner
+ android:id="@+id/ipv6"
+ android:layout_width="0dip"
+ android:layout_weight="75"
+ android:layout_height="wrap_content"
+ android:drawSelectorOnTop="false"/>
+ </LinearLayout>
+
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:stackFromBottom="true"
+ android:transcriptMode="alwaysScroll"/>
+</LinearLayout>
diff --git a/java/android/controller/src/main/res/raw/icon.png b/java/android/controller/src/main/res/raw/icon.png
new file mode 100644
index 00000000000..75024841d32
--- /dev/null
+++ b/java/android/controller/src/main/res/raw/icon.png
Binary files differ
diff --git a/java/android/controller/src/main/res/values/strings.xml b/java/android/controller/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..46a3b776b80
--- /dev/null
+++ b/java/android/controller/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Test Controller</string>
+</resources>