Add EpmController class.
diff --git a/src/com/projectara/araepm/EpmController.java b/src/com/projectara/araepm/EpmController.java
new file mode 100644
index 0000000..4923d7e
--- /dev/null
+++ b/src/com/projectara/araepm/EpmController.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.projectara.araepm;
+
+import java.io.IOException;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+
+import static android.content.Context.I2C_SERVICE;
+
+import android.hardware.I2cManager;
+
+public class EpmController {
+
+ // EPM mailbox semantics:
+ //
+ // - Bit[31]: set this to indicate need to change attach state.
+ // (If this is already set, a state change is ongoing.
+ // This shouldn't happen under ordinary circumstances.)
+ //
+ // - Bit[30]: if set, attach the EPM. If unset, detach the EPM.
+ //
+ // - Bits[7:0]: port to change attach state of.
+ //
+ // After executing the EPM action, the SVC will clear bit 31 and
+ // put a result code into Bits[15:0], which is zero on success.
+
+ // How long to wait for an EPM state change (milliseconds).
+ private static final long TIMEOUT_MS = 10000;
+
+ // The EPM mailbox UniPro attribute.
+ private static final DME.AttributeId MBOX = DME.AttributeId.DME_MAILBOX;
+
+ // The application processort port
+ //
+ // FIXME we should read this from the DME so we know where we are.
+ // Is there a way? Does it matter?
+ private static final int HACK_MY_PORT = 0;
+
+ private static final int MAX_ENDO_PORT = 0xff;
+ private static final int MIN_ENDO_PORT = 0;
+
+ private static final String TAG = "araepm";
+
+ public static enum EpmState {
+ EPM_ATTACH,
+ EPM_DETACH;
+ }
+
+ public static enum EpmChangeStatus {
+ EPM_OK,
+ EPM_ERROR,
+ EPM_TIMEOUT;
+ }
+
+ public interface EpmChangeCallback {
+ public void onEpmChangeFinished(EpmState state,
+ EpmChangeStatus status,
+ int port);
+ }
+
+ private DME dme;
+ private EpmChangeCallback callback;
+
+ private HandlerThread handlerThread;
+ private Handler handler;
+
+ public EpmController(Context context,
+ EpmChangeCallback callback) {
+ I2cManager i2c = (I2cManager)context.getSystemService(I2C_SERVICE);
+ this.dme = new DME(i2c);
+ this.callback = callback;
+ }
+
+ private boolean isStartedLocked() {
+ return handlerThread != null;
+ }
+
+ private void ensureStartedLocked() {
+ if (!isStartedLocked()) {
+ throw new IllegalStateException("not yet started");
+ }
+ }
+
+ public synchronized void start() {
+ if (isStartedLocked()) {
+ throw new IllegalStateException("already started");
+ }
+ handlerThread = new HandlerThread("EpmController");
+ handlerThread.start();
+ Looper looper = handlerThread.getLooper();
+ if (looper == null) {
+ Log.wtf(TAG, "can't get handler thread looper");
+ throw new RuntimeException();
+ }
+ handler = new Handler(looper);
+ }
+
+ public synchronized void stop() {
+ if (!isStartedLocked()) {
+ throw new IllegalStateException("not yet started");
+ }
+ handlerThread.quit();
+ while (handlerThread.isAlive())
+ ;
+ handler = null;
+ }
+
+ private void doAttachDetachLocked(int port, EpmState desired) {
+ if (port > MAX_ENDO_PORT || port < MIN_ENDO_PORT) {
+ Log.wtf(TAG, "Invalid port value: " + port);
+ callback.onEpmChangeFinished(desired, EpmChangeStatus.EPM_ERROR,
+ port);
+ return;
+ }
+
+ ensureStartedLocked();
+ handler.post(new EpmStateChanger(port, desired));
+ }
+
+ public synchronized void attachEpm(int port) {
+ doAttachDetachLocked(port, EpmState.EPM_ATTACH);
+ }
+
+ public synchronized void detachEpm(int port) {
+ doAttachDetachLocked(port, EpmState.EPM_DETACH);
+ }
+
+ private class EpmStateChanger implements Runnable {
+ private final int port;
+ private final EpmState desired;
+ private final int mBoxValue;
+
+ private static final int S_IDX = 0; // selector index
+ private static final int CHANGING_BIT = 31;
+ private static final int ATTACH_BIT = 30;
+ private static final int PORT_MASK = 0xff;
+ private static final int RESULT_MASK = 0xffff;
+
+ private static final boolean PEER_MBOX = true;
+
+ public EpmStateChanger(int port, EpmState desired) {
+ this.port = port;
+ this.desired = desired;
+ this.mBoxValue = ((1 << CHANGING_BIT) |
+ ((desired == EpmState.EPM_ATTACH ? 1 : 0)
+ << ATTACH_BIT) |
+ (port & PORT_MASK));
+ }
+
+ private int getMBox() throws IOException {
+ return dme.readDMEConfig(HACK_MY_PORT, PEER_MBOX, MBOX, S_IDX, 0);
+ }
+
+ private boolean isEpmChangeOngoing(int mbox) {
+ return (mbox & (1 << CHANGING_BIT)) != 0;
+ }
+
+ private void startStateChange() throws IOException {
+ dme.writeDMEConfig(HACK_MY_PORT, PEER_MBOX, MBOX, S_IDX, mBoxValue,
+ 0);
+ }
+
+ private void finish(EpmChangeStatus status) {
+ callback.onEpmChangeFinished(desired, status, port);
+ }
+
+ @Override
+ public void run() {
+ long start = SystemClock.elapsedRealtime();
+ boolean started = false;
+
+ // FIXME try/finally a wake lock here -- this is a bad
+ // time to go to sleep
+ while (true) {
+ int mbox;
+
+ // Bail on timeout.
+ if (SystemClock.elapsedRealtime() - start > TIMEOUT_MS) {
+ finish(EpmChangeStatus.EPM_TIMEOUT);
+ return;
+ }
+
+ // Check the mailbox.
+ try {
+ mbox = getMBox();
+ } catch (IOException ioe) {
+ Log.e(TAG, "can't read EPM mailbox: " + ioe);
+ finish(EpmChangeStatus.EPM_ERROR);
+ return;
+ }
+
+ // Try to start the EPM state change, allowing for
+ // a previously ongoing state change to continue.
+ if (!started) {
+ if (!isEpmChangeOngoing(mbox)) {
+ try {
+ startStateChange();
+ } catch (IOException ioe) {
+ Log.e(TAG, "can't request EPM change: " + ioe);
+ finish(EpmChangeStatus.EPM_ERROR);
+ return;
+ }
+ started = true;
+ }
+ continue;
+ }
+
+ // Wait for the change to finish, and relay the result
+ // to the callback.
+ if (isEpmChangeOngoing(mbox)) {
+ continue;
+ }
+ int exitStatus = mbox & RESULT_MASK;
+ finish(exitStatus == 0 ?
+ EpmChangeStatus.EPM_OK :
+ EpmChangeStatus.EPM_ERROR);
+ return;
+ }
+ }
+ }
+}