Reimplement the application logic.

New features/improvements:

- timeouts
- better (though still not great) error handling
- hooks for better app developers to make this prettier
- the ability to address EPMs on arbitrary ports (uses EpmController)
- proper threading (via EpmController)
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
index 5ebc2f8..6e87eb6 100644
--- a/res/layout/activity_main.xml
+++ b/res/layout/activity_main.xml
@@ -1,25 +1,69 @@
 <LinearLayout 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"
-    android:paddingBottom="@dimen/activity_vertical_margin"
-    android:paddingLeft="@dimen/activity_horizontal_margin"
-    android:paddingRight="@dimen/activity_horizontal_margin"
-    android:paddingTop="@dimen/activity_vertical_margin"
-    tools:context=".MainActivity" >
+              xmlns:tools="http://schemas.android.com/tools"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:paddingBottom="@dimen/activity_vertical_margin"
+              android:paddingLeft="@dimen/activity_horizontal_margin"
+              android:paddingRight="@dimen/activity_horizontal_margin"
+              android:paddingTop="@dimen/activity_vertical_margin"
+              tools:context=".MainActivity" >
 
-<Button
+  <LinearLayout
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:tools="http://schemas.android.com/tools"
+      android:orientation="horizontal"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      tools:context=".MainActivity">
+
+    <TextView
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:layout_weight="1"
-        android:text="@string/EPM_ATTACH"
+        android:text="@string/port_input"/>
+
+    <EditText
+        android:id="@+id/portNumber"
+        android:layout_height="wrap_content"
+        android:layout_width="fill_parent"
+        android:layout_weight="1"
+        android:inputType="number"
+        android:text="@string/port_number_initial"/>
+
+  </LinearLayout>
+
+  <LinearLayout
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:tools="http://schemas.android.com/tools"
+      android:orientation="horizontal"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      tools:context=".MainActivity">
+
+    <Button
+        android:layout_height="wrap_content"
+        android:layout_width="fill_parent"
+        android:layout_weight="1"
+        android:text="@string/epm_attach"
+        android:id="@+id/attachButton"
         android:onClick="attachEpm" />
 
-<Button
+    <Button
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"
         android:layout_weight="1"
-        android:text="@string/EPM_DETACH"
+        android:id="@+id/detachButton"
+        android:text="@string/epm_detach"
         android:onClick="detachEpm" />
 
+  </LinearLayout>
+
+  <TextView
+      android:id="@+id/statusText"
+      android:layout_height="wrap_content"
+      android:layout_width="fill_parent"
+      android:layout_weight="1"
+      android:text="@string/status_text_initial"/>
+
 </LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 399110e..a3ea92e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4,7 +4,25 @@
     <string name="app_name">AraEPM</string>
     <string name="action_settings">Settings</string>
     <string name="hello_world">Hello world!</string>
-    <string name="EPM_ATTACH">Attach EPM</string>
-    <string name="EPM_DETACH">Detach EPM</string>
+    <string name="epm_attach">Attach EPM</string>
+    <string name="epm_detach">Detach EPM</string>
+    <string name="port_input">Port number:</string>
+    <string name="port_number_initial">1</string>
+    <string name="status_text_initial">Ready</string>
+
+    <!-- EPM change status result text strings -->
+    <string name="epm_attached">attached</string>
+    <string name="epm_detached">detached</string>
+    <!-- EPM state change text strings. Argument: port number. -->
+    <string name="epm_attaching">Attaching port %1$d...</string>
+    <string name="epm_detaching">Detaching port %1$d...</string>
+    <!-- EPM state change status update strings.
+
+         Arguments:
+         - 1 (string): state, epm_attached or epm_detached
+         - 2 (int): port number -->
+    <string name="epm_status_ok">Ok: %1$s port %2$d</string>
+    <string name="epm_status_error">ERROR: port %2$d not %1$s</string>
+    <string name="epm_status_timeout">Timed out: port %2$d not %1$s</string>
 
 </resources>
diff --git a/src/com/projectara/araepm/MainActivity.java b/src/com/projectara/araepm/MainActivity.java
index 5067760..2981c9e 100644
--- a/src/com/projectara/araepm/MainActivity.java
+++ b/src/com/projectara/araepm/MainActivity.java
@@ -4,21 +4,57 @@
 import android.app.Activity;
 import android.view.Menu;
 import android.view.View;
-import android.hardware.I2cManager;
-import android.hardware.I2cTransaction;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
 import java.io.IOException;
 import android.os.Handler;
 import android.util.Log;
 
-public class MainActivity extends Activity {
+public class MainActivity extends Activity
+    implements EpmController.EpmChangeCallback {
 
     private static final String TAG = "araepm";
-    private DME dme = null;
+
+    private static String EPM_ATTACHED;
+    private static String EPM_DETACHED;
+
+    private static String EPM_ATTACHING;
+    private static String EPM_DETACHING;
+
+    private static String EPM_STATUS_OK;
+    private static String EPM_STATUS_ERROR;
+    private static String EPM_STATUS_TIMEOUT;
+
+    private TextView statusText;
+    private EditText portNumber;
+    private Handler handler;
+    private Button[] buttons;
+
+    private EpmController epmController;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
+        portNumber = (EditText)findViewById(R.id.portNumber);
+        statusText = (TextView)findViewById(R.id.statusText);
+
+        EPM_ATTACHED = getString(R.string.epm_attached);
+        EPM_DETACHED = getString(R.string.epm_detached);
+
+        EPM_ATTACHING = getString(R.string.epm_attaching);
+        EPM_DETACHING = getString(R.string.epm_detaching);
+
+        EPM_STATUS_OK = getString(R.string.epm_status_ok);
+        EPM_STATUS_ERROR = getString(R.string.epm_status_error);
+        EPM_STATUS_TIMEOUT = getString(R.string.epm_status_timeout);
+
+        buttons = new Button[2];
+        buttons[0] = (Button)findViewById(R.id.attachButton);
+        buttons[1] = (Button)findViewById(R.id.detachButton);
+
+        handler = new Handler();
     }
 
     @Override
@@ -32,42 +68,110 @@
     protected void onStart(){
         Log.d(TAG, "onstart");
         super.onStart();
-        I2cManager i2c = (I2cManager) getSystemService(I2C_SERVICE);
-        dme = new DME(i2c);
+        epmController = new EpmController(this, this);
+        epmController.start();
     }
 
     @Override
     protected void onStop() {
         Log.d(TAG, "onstop");
-    	super.onStop();
+        super.onStop();
+        epmController.stop();
+        epmController = null;
+    }
+
+    private void disableStateChanges() {
+        for (int i = 0; i < buttons.length; i++) {
+            buttons[i].setClickable(false);
+        }
+    }
+
+    private void enableStateChanges() {
+        for (int i = 0; i < buttons.length; i++) {
+            buttons[i].setClickable(true);
+        }
+    }
+
+    private int getPortNumber() {
+        String s = portNumber.getText().toString();
+        try {
+            return Integer.parseInt(s);
+        } catch (NumberFormatException nfe) {
+            // Can't happen; portNumber only allows numeric input
+            Log.wtf(TAG, "non-numeric text " + s + " in portNumber");
+            return -1;
+        }
+    }
+
+    private void doAttachDetach(boolean attach) {
+        int port = getPortNumber();
+        if (port < 0) {
+            return;
+        }
+        Log.d(TAG,
+              (attach ? "Attaching" : "Detaching") + " EPM on port " + port);
+        disableStateChanges();
+        if (attach) {
+            statusText.setText(String.format(EPM_ATTACHING, port));
+            epmController.attachEpm(port);
+        } else {
+            statusText.setText(String.format(EPM_DETACHING, port));
+            epmController.detachEpm(port);
+        }
     }
 
     public void attachEpm(View view) {
-        int rc;
-        Log.d(TAG, "EPM attach button pressed");
-        try {
-            dme.writeDMEConfig(0,
-                               true,
-                               DME.AttributeId.DME_MAILBOX,
-                               0,
-                               1,
-                               0);
-        } catch (IOException e) {
-            return;
-        }
+        doAttachDetach(true);
     }
 
     public void detachEpm(View view) {
-        int rc;
-        Log.d(TAG, "EPM detach button pressed");
-        try {
-            dme.writeDMEConfig(0, true,
-                               DME.AttributeId.DME_MAILBOX,
-                               0,
-                               2,
-                               0);
-        } catch (IOException e) {
-            return;
-        }
+        doAttachDetach(false);
+    }
+
+    @Override
+    public void onEpmChangeFinished(EpmController.EpmState attemptedState,
+                                    EpmController.EpmChangeStatus changeStatus,
+                                    int changedPort) {
+        final EpmController.EpmState attempted = attemptedState;
+        final EpmController.EpmChangeStatus status = changeStatus;
+        final int port = changedPort;
+        handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    String newStatus;
+                    String s;
+
+                    enableStateChanges();
+
+                    switch (attempted) {
+                    case EPM_ATTACH:
+                        s = EPM_ATTACHED;
+                        break;
+                    case EPM_DETACH:
+                        s = EPM_DETACHED;
+                        break;
+                    default:
+                        Log.wtf(TAG, "can't happen");
+                        return;
+                    }
+
+                    String statusFormat;
+                    switch (status) {
+                    case EPM_OK:
+                        statusFormat = EPM_STATUS_OK;
+                        break;
+                    case EPM_ERROR:
+                        statusFormat = EPM_STATUS_ERROR;
+                        break;
+                    case EPM_TIMEOUT:
+                        statusFormat = EPM_STATUS_TIMEOUT;
+                        break;
+                    default:
+                        Log.wtf(TAG, "can't happen");
+                        return;
+                    }
+                    statusText.setText(String.format(statusFormat, s, port));
+                }
+            });
     }
 }