Complete rewrite.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 569549f..709aa7e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5,7 +5,7 @@
     android:versionName="1.0" >
 
     <uses-sdk
-        android:minSdkVersion="8"
+        android:minSdkVersion="14"
         android:targetSdkVersion="19" />
 
     <application
@@ -23,7 +23,4 @@
             </intent-filter>
         </activity>
     </application>
-
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
 </manifest>
diff --git a/assets/plox.dat b/assets/plox.dat
deleted file mode 100644
index bf1e6c5..0000000
--- a/assets/plox.dat
+++ /dev/null
Binary files differ
diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar
deleted file mode 100644
index a7e9919..0000000
--- a/libs/android-support-v4.jar
+++ /dev/null
Binary files differ
diff --git a/res/layout/activity_ara_plox.xml b/res/layout/activity_ara_plox.xml
index 4063dbb..a0d8222 100644
--- a/res/layout/activity_ara_plox.xml
+++ b/res/layout/activity_ara_plox.xml
@@ -1,41 +1,35 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical" >
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:keepScreenOn="true">
     <TextView
         android:id="@+id/textView"
-        android:layout_width="fill_parent"
-        android:layout_height="0dip"
+        android:layout_width="match_parent"
+        android:layout_height="28sp"
         android:gravity="center"
-        android:layout_weight="1"
         android:background="#FFFFFF"
         android:fontFamily="sans-serif"
-        android:textSize="32sp"
-        android:text="@string/hello_world" />
+        android:textSize="20sp"
+        android:text="@string/chart1_name"/>
     <LinearLayout android:id="@+id/irChart"
                   android:orientation="horizontal"
-                  android:layout_width="fill_parent"
-                  android:layout_height="0dip"
-                  android:layout_weight="3"/>
-    <LinearLayout android:id="@+id/redChart"
-                  android:orientation="horizontal"
-                  android:layout_width="fill_parent"
-                  android:layout_height="0dip"
-                  android:layout_weight="3"/>
-
-
-<!--
+                  android:layout_width="match_parent"
+                  android:layout_height="0dp"
+                  android:layout_weight="1"/>
     <TextView
         android:id="@+id/textView"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_height="28sp"
         android:gravity="center"
-        android:layout_weight="1"
         android:background="#FFFFFF"
         android:fontFamily="sans-serif"
-        android:textSize="64sp"
-        android:text="@string/hello_world" />
--->
-
+        android:textSize="20sp"
+        android:text="@string/chart2_name"/>
+    <LinearLayout android:id="@+id/redChart"
+                  android:orientation="horizontal"
+                  android:layout_width="match_parent"
+                  android:layout_height="0dp"
+                  android:layout_weight="1"/>
 </LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8c5ee37..8acf43f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <string name="app_name">AraPloxIO</string>
-    <string name="hello_world">Pulse Monitor</string>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">AraPulseOx</string>
+    <string name="chart1_name">940nm</string>
+    <string name="chart2_name">660nm</string>
 </resources>
diff --git a/src/com/google/araploxio/AFE4400Thread.java b/src/com/google/araploxio/AFE4400Thread.java
deleted file mode 100644
index 4193341..0000000
--- a/src/com/google/araploxio/AFE4400Thread.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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.google.araploxio;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.hardware.I2cManager;
-import android.hardware.I2cTransaction;
-import android.util.Log;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-import java.io.DataOutputStream;
-import java.io.DataInputStream;
-import java.io.FileOutputStream;
-
-public class AFE4400Thread extends Thread {
-
-    // The 7-bit slave address
-    private static final int address = (0x50 >> 1);
-    private static final String TAG = "araplox";
-
-    private static final String preferredI2cBus = "/dev/i2c-4";
-
-    private static final boolean useRecordedData = false;
-
-    private Handler handler;
-    private I2cManager i2c;
-    private Context context;
-
-    private volatile boolean stopped;
-    private DataInputStream ploxInputStream;
-
-    private static final I2cTransaction[] setupWrites = {
-        WriteReg(0x00, 0x00, 0x00, 0x00), //AFE4400_CONTROL0
-        WriteReg(0x01, 0x00, 0x17, 0xD4), //AFE4400_LED2STC
-        WriteReg(0x02, 0x00, 0x1D, 0xAE), //AFE4400_LEDENDC
-        WriteReg(0x03, 0x00, 0x17, 0x70), //AFE4400_LED2LEDSTC
-        WriteReg(0x04, 0x00, 0x1D, 0xAF), //AFE4400_LED2LEDENDC
-        WriteReg(0x05, 0x00, 0x00, 0x00), //AFE4400_ALED2STC
-        WriteReg(0x06, 0x00, 0x06, 0x3E), //AFE4400_ALED2ENDC
-        WriteReg(0x07, 0x00, 0x08, 0x34), //AFE4400_LED1STC
-        WriteReg(0x08, 0x00, 0x0E, 0x0E), //AFE4400_LED1ENDC
-        WriteReg(0x09, 0x00, 0x07, 0xD0), //AFE4400_LED1LEDSTC
-        WriteReg(10, 0x00, 0x0E, 0x0F), //AFE4400_LED1LEDENDC
-
-        WriteReg(11, 0x00, 0x0F, 0xA0), //AFE4400_ALED1STC
-        WriteReg(12, 0x00, 0x15, 0xDE), //AFE4400_ALED1ENDC
-        WriteReg(13, 0x00, 0x00, 0x02), //AFE4400_LED2CONVST
-        WriteReg(14, 0x00, 0x07, 0xCF), //AFE4400_LED2CONVEND
-        WriteReg(15, 0x00, 0x07, 0xD2), //AFE4400_ALED2CONVST
-        WriteReg(16, 0x00, 0x0F, 0x9F), //AFE4400_ALED2CONVEND
-        WriteReg(17, 0x00, 0x0F, 0xA2), //AFE4400_LED1CONVST
-        WriteReg(18, 0x00, 0x17, 0x6F), //AFE4400_LED1CONVEND
-        WriteReg(19, 0x00, 0x17, 0x72), //AFE4400_ALED1CONVST
-        WriteReg(20, 0x00, 0x1F, 0x3F), //AFE4400_ALED1CONVEND
-
-        WriteReg(21, 0x00, 0x00, 0x00),//AFE4400_ADCRSTSTCT0
-        WriteReg(22, 0x00, 0x00, 0x00),//AFE4400_ADCRSTENDCT0
-        WriteReg(23, 0x00, 0x07, 0xD0),//AFE4400_ADCRSTSTCT1
-        WriteReg(24, 0x00, 0x07, 0xD0),//AFE4400_ADCRSTENDCT1
-        WriteReg(25, 0x00, 0x0F, 0XA0),//AFE4400_ADCRSTSTCT2
-        WriteReg(26, 0x00, 0x0F, 0XA0),//AFE4400_ADCRSTENDCT2
-        WriteReg(27, 0x00, 0x17, 0x70),//AFE4400_ADCRSTSTCT3
-        WriteReg(28, 0x00, 0x17, 0x70),//AFE4400_ADCRSTENDCT3
-        WriteReg(29, 0x00, 0x1F, 0x3F),//AFE4400_PRPCOUNT
-        WriteReg(30, 0x00, 0x01, 0x01),//AFE4400_CONTROL1
-
-        WriteReg(31, 0x00, 0x00, 0x00),//AFE4400_SPARE1
-        WriteReg(32, 0x00, 0x00, 0x00),//AFE4400_TIAGAIN
-        WriteReg(33, 0x00, 0x00, 0x0A),//AFE4400_TIA_AMB_GAIN
-        WriteReg(34, 0x01, 0x14, 0x29),//AFE4400_LEDCNTRL
-        WriteReg(35, 0x02, 0x01, 0x00),//AFE4400_CONTROL2
-        WriteReg(36, 0x00, 0x00, 0x00),//AFE4400_SPARE2
-        WriteReg(37, 0x00, 0x00, 0x00),//AFE4400_SPARE3
-        WriteReg(38, 0x00, 0x00, 0x00),//AFE4400_SPARE4
-        WriteReg(39, 0x00, 0x00, 0x00),//AFE4400_RESERVED1
-        WriteReg(40, 0x00, 0x00, 0x00),//AFE4400_RESERVED2
-        WriteReg(41, 0x00, 0x00, 0x00),//AFE4400_ALARM
-        WriteReg(42, 0x00, 0x00, 0x00),//AFE4400_LED2VAL
-
-        WriteReg(43, 0x00, 0x00, 0x00),//AFE4400_ALED2VAL
-        WriteReg(44, 0x00, 0x00, 0x00),//AFE4400_LED1VAL
-        WriteReg(45, 0x00, 0x00, 0x00),//AFE4400_ALED1VAL
-        WriteReg(46, 0x00, 0x00, 0x00),//AFE4400_LED2-ALED2VAL
-        WriteReg(47, 0x00, 0x00, 0x00),//AFE4400_LED1-ALED1VAL
-        WriteReg(48, 0x00, 0x00, 0x00),//AFE4400_DIAG
-
-        // Setup SPI_READ
-        WriteReg(0, 0x00, 0x00, 0x01),//AFE4400_DIAG
-    };
-
-    public AFE4400Thread(Context context, Handler handler) {
-        this.context = context;
-        this.i2c = (I2cManager)context.getSystemService(Context.I2C_SERVICE);
-        this.stopped = false;
-        this.handler = handler;
-    }
-
-    private void startRecordedStream() {
-        Log.d(TAG, "Playing back recorded data");
-
-        try {
-            ploxInputStream = new DataInputStream(context.getAssets().open("plox.dat"));
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-
-        int x = 0;
-        while (!stopped) {
-            Message msg;
-            try {
-                msg = handler.obtainMessage(0, ploxInputStream.readInt(), ploxInputStream.readInt());
-                msg.sendToTarget();
-            } catch (IOException e1) {
-                e1.printStackTrace();
-                requestStop();
-                break;
-            }
-            try {
-                Thread.sleep(10);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-
-        try {
-            ploxInputStream.close();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void startSensorStream() {
-        String[] buses = i2c.getI2cBuses();
-        if (buses.length == 0) {
-            setText("no I2C buses found :(");
-            return;
-        }
-        String bus = buses[0];
-        for (String b: buses) {
-            if (b.equals(preferredI2cBus)) {
-                bus = b;
-            }
-        }
-        Log.i(TAG, "[pulse ox app] Using I2C bus: " + bus);
-
-        I2cTransaction[] results;
-        for (I2cTransaction txn: setupWrites) {
-            try {
-                results = i2c.performTransactions(bus, address, txn);
-            } catch (IOException e) {
-                setText("transaction error: " + e);
-                return;
-            }
-        }
-
-        while (!isStopped()) {
-            results = null;
-            byte[] data;
-            int LED1VAL;
-            int LED2VAL;
-            I2cTransaction[] txns0 = {
-                I2cTransaction.newWrite(0x01,                 // Select SPI device 1
-                                        0x2e,                 // LED2-ALED2VAL
-                                        0xff, 0xff, 0xff),    // 3 dummy bytes
-                I2cTransaction.newRead(4),
-            };
-            I2cTransaction[] txns1 = {
-                I2cTransaction.newWrite(0x01,                 // Select SPI device 1
-                                        0x2f,                 // LED2-ALED2VAL
-                                        0xff, 0xff, 0xff),    // 3 dummy bytes
-                I2cTransaction.newRead(4),
-            };
-
-
-            try {
-                for (I2cTransaction txn: txns0) {
-                    results = i2c.performTransactions(bus, address, txn);
-                }
-            } catch (IOException e) {
-                setText("error while reading back: " + e);
-                return;
-            }
-
-            data = results[0].data;
-
-            LED1VAL = (((int)data[1]) << 16) |
-                      (((int)data[2] & 0xFF) <<  8) |
-                      (((int)data[3] & 0xFF));
-
-            try {
-                for (I2cTransaction txn: txns1) {
-                    results = i2c.performTransactions(bus, address, txn);
-                }
-            } catch (IOException e) {
-                setText("error while reading back: " + e);
-                return;
-            }
-
-            data = results[0].data;
-
-            LED2VAL = (((int)data[1]) << 16) |
-                      (((int)data[2] & 0xFF) <<  8) |
-                      (((int)data[3] & 0xFF));
-
-            Message msg = handler.obtainMessage(0, LED1VAL, LED2VAL);
-            msg.sendToTarget();
-
-            try {
-                Thread.sleep(5);
-            } catch (Exception e) {
-                e.printStackTrace();
-                requestStop();
-            }
-        }
-    }
-
-    @Override
-    public void run() {
-        if (useRecordedData) {
-            startRecordedStream();
-        } else {
-            startSensorStream();
-        }
-    }
-
-
-    private static I2cTransaction WriteReg(int reg, int b1, int b2, int b3) {
-        return I2cTransaction.newWrite(0x01, reg, b1, b2, b3);
-    }
-
-    private void setText(String s) {
-        // TODO -- it'd be nice to actually have a text field for this
-        // stuff.
-        Log.i(TAG, s);
-    }
-
-    // ------------------------------------------------------------
-
-    public void requestStop() {
-        this.stopped = true;
-    }
-
-    public boolean isStopped() {
-        return this.stopped;
-    }
-}
diff --git a/src/com/google/araploxio/AraPloxActivity.java b/src/com/google/araploxio/AraPloxActivity.java
index bc9a0af..f04ead3 100644
--- a/src/com/google/araploxio/AraPloxActivity.java
+++ b/src/com/google/araploxio/AraPloxActivity.java
@@ -16,177 +16,46 @@
 
 package com.google.araploxio;
 
-import org.achartengine.ChartFactory;
-import org.achartengine.GraphicalView;
-import org.achartengine.chart.PointStyle;
-import org.achartengine.model.XYMultipleSeriesDataset;
-import org.achartengine.model.XYSeries;
-import org.achartengine.renderer.XYMultipleSeriesRenderer;
-import org.achartengine.renderer.XYSeriesRenderer;
-
 import android.app.Activity;
 import android.graphics.Color;
-import android.graphics.Paint.Align;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.util.Log;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.ViewGroup.LayoutParams;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 public class AraPloxActivity extends Activity {
-	private static final String TAG = "AraPlox/Activity";
-    private static final int SCROLLBACK_SIZE = 200;
+    private Chart irChart, redChart;
+    private Sensor sensor;
 
-	private static TextView bpmTextView;
-	private static Handler mHandler = null;
-	private static AFE4400Thread afe4400Thread = null;
-    private static int sampleCount = 0;
-
-    private AraPloxChart irPloxChart = new AraPloxChart(R.id.irChart);
-    private AraPloxChart redPloxChart = new AraPloxChart(R.id.redChart);
-
-    private class AraPloxChart {
-        int id;
-        private XYMultipleSeriesRenderer renderer;
-        private XYMultipleSeriesDataset dataSet;
-        private XYSeriesRenderer xySeriesRenderer;
-        private XYSeries xySeries;
-        private GraphicalView chartView;
-
-        public AraPloxChart(int id) {
-            this.id = id;
+    private final Handler handler = new Handler() {
+        public void handleMessage(Message msg) {
+            irChart.add(msg.arg1);
+            redChart.add(msg.arg2);
         }
+    };
 
-        public void makeChart(int pointColor) {
-            dataSet = new XYMultipleSeriesDataset();
-            xySeries = new XYSeries("");
-            dataSet.addSeries(xySeries);
-
-            int[] colors = new int[] {pointColor};
-            PointStyle[] styles = new PointStyle[] {PointStyle.CIRCLE};
-
-            renderer = new XYMultipleSeriesRenderer();
-
-            int length = colors.length;
-            for (int i = 0; i < length; i++) {
-                xySeriesRenderer = new XYSeriesRenderer();
-                xySeriesRenderer.setFillPoints(true);
-                xySeriesRenderer.setLineWidth(10.0f);
-                xySeriesRenderer.setPointStrokeWidth(1);
-                xySeriesRenderer.setColor(colors[i]);
-                xySeriesRenderer.setPointStyle(styles[i]);
-                renderer.addSeriesRenderer(xySeriesRenderer);
-            }
-
-            renderer.setChartTitleTextSize(20);
-            renderer.setLabelsTextSize(15);
-            renderer.setLegendTextSize(15);
-            renderer.setPointSize(5f);
-            renderer.setMargins(new int[] { 50, 50, 50, 50 });
-            renderer.setXAxisMin(0);
-            renderer.setXAxisMax(10);
-            renderer.setYAxisMin(0);
-            renderer.setYAxisMax(100);
-            renderer.setAxesColor(Color.BLACK);
-            renderer.setLabelsColor(Color.BLACK);
-            renderer.setShowLegend(false);
-            renderer.setXLabels(10);
-            renderer.setYLabels(0);
-            renderer.setShowGrid(false);
-            renderer.setXLabelsAlign(Align.CENTER);
-            renderer.setApplyBackgroundColor(true);
-            renderer.setBackgroundColor(Color.WHITE);
-            renderer.setMarginsColor(Color.WHITE);
-
-            chartView = ChartFactory.getCubeLineChartView(getApplicationContext(), dataSet, renderer, 1.0f);
-
-            LinearLayout layout = (LinearLayout)findViewById(id);
-            layout.addView(chartView, new LayoutParams(LayoutParams.FILL_PARENT,
-                        LayoutParams.FILL_PARENT));
-            chartView.repaint();
-        }
-
-        public void removeChart() {
-            LinearLayout layout = (LinearLayout)findViewById(id);
-            layout.removeView(chartView);
-        }
-
-        public void handleNewSample(double x, double y) {
-            xySeries.add(x, y);
-
-            // Scroll continuously after SCROLLBACK_SIZE points
-            if (x > SCROLLBACK_SIZE) {
-                xySeries.remove(0);
-            }
-
-            // Autoscale
-            double maxX = xySeries.getMaxX();
-            double minX = xySeries.getMinX();
-            double maxY = xySeries.getMaxY();
-            double minY = xySeries.getMinY();
-
-            maxY *= 1.01;
-            minY *= .99;
-
-            renderer.setXAxisMax(maxX);
-            renderer.setXAxisMin(minX);
-            renderer.setYAxisMax(maxY);
-            renderer.setYAxisMin(minY);
-
-            chartView.repaint();
-
-            sampleCount++;
-        }
-    }
-
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Log.d(TAG, "onCreate()");
-        requestWindowFeature(Window.FEATURE_NO_TITLE);
         setContentView(R.layout.activity_ara_plox);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-        mHandler = new Handler() {
-            public void handleMessage(Message msg) {
-                double irLedSample  = msg.arg1;
-                double redLedSample = msg.arg2;
-
-                irPloxChart.handleNewSample(sampleCount, irLedSample);
-                redPloxChart.handleNewSample(sampleCount, redLedSample);
-
-                sampleCount++;
-            }
-        };
+        irChart = new Chart(this, (LinearLayout) findViewById(R.id.irChart), "ir", Color.GREEN);
+        redChart = new Chart(this, (LinearLayout) findViewById(R.id.redChart), "red", Color.RED);
     }
 
+    @Override
     protected void onResume() {
+        irChart.clear();
+        redChart.clear();
         super.onResume();
-        Log.d(TAG, "onResume()");
-        if (afe4400Thread == null) {
-            afe4400Thread = new AFE4400Thread(getApplicationContext(), mHandler);
-            afe4400Thread.start();
-        }
-        irPloxChart.makeChart(Color.GREEN);
-        redPloxChart.makeChart(Color.RED);
+        sensor = new Sensor(this, handler);
+        sensor.start();
     }
 
+    @Override
     protected void onPause() {
+        sensor.stop();
+        sensor = null;
+        handler.removeMessages(0);
         super.onPause();
-        Log.d(TAG, "onPause()");
-        if (afe4400Thread != null) {
-            afe4400Thread.requestStop();
-            afe4400Thread = null;
-        }
-        irPloxChart.removeChart();
-        redPloxChart.removeChart();
-    }
-
-    protected void onStop() {
-        super.onStop();
-        Log.d(TAG, "onStop()");
     }
 }
diff --git a/src/com/google/araploxio/Chart.java b/src/com/google/araploxio/Chart.java
new file mode 100644
index 0000000..e8ae9c0
--- /dev/null
+++ b/src/com/google/araploxio/Chart.java
@@ -0,0 +1,113 @@
+/*
+ * 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.google.araploxio;
+
+import org.achartengine.ChartFactory;
+import org.achartengine.GraphicalView;
+import org.achartengine.model.XYMultipleSeriesDataset;
+import org.achartengine.model.XYSeries;
+import org.achartengine.renderer.XYMultipleSeriesRenderer;
+import org.achartengine.renderer.XYSeriesRenderer;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Paint.Align;
+import android.widget.LinearLayout;
+
+public class Chart {
+    private static final int SCROLLBACK_SIZE = 200;
+
+    private int sampleCount = 0;
+    private XYMultipleSeriesDataset mDataset;
+    private XYMultipleSeriesRenderer mRenderer;
+    private XYSeries mCurrentSeries;
+    private XYSeriesRenderer mCurrentRenderer;
+    private GraphicalView mChart;
+
+    public Chart(Context context, LinearLayout layout, String name, int color) {
+        mDataset = new XYMultipleSeriesDataset();
+
+        mRenderer = new XYMultipleSeriesRenderer();
+        mRenderer.setAxisTitleTextSize(0);
+        mRenderer.setChartTitleTextSize(0);
+        mRenderer.setLabelsTextSize(15);
+        mRenderer.setLegendTextSize(15);
+        mRenderer.setPointSize(1.0f);
+        mRenderer.setMargins(new int[] { 10, 30, 30, 30 });
+        mRenderer.setXAxisMin(0);
+        mRenderer.setXAxisMax(10);
+        mRenderer.setYAxisMin(0);
+        mRenderer.setYAxisMax(100);
+        mRenderer.setAxesColor(Color.BLACK);
+        mRenderer.setLabelsColor(Color.BLACK);
+        mRenderer.setLegendHeight(0);
+        mRenderer.setShowLegend(false);
+        mRenderer.setXLabels(0);
+        mRenderer.setYLabels(2);
+        mRenderer.setShowGrid(false);
+        mRenderer.setXLabelsAlign(Align.CENTER);
+        mRenderer.setYLabelsAlign(Align.LEFT);
+        mRenderer.setApplyBackgroundColor(true);
+        mRenderer.setBackgroundColor(Color.WHITE);
+        mRenderer.setMarginsColor(Color.WHITE);
+        mRenderer.setAntialiasing(false);
+        mRenderer.setPanEnabled(false, false);
+        mRenderer.setZoomEnabled(false, false);
+
+        mCurrentSeries = new XYSeries(name);
+        mDataset.addSeries(mCurrentSeries);
+
+        mCurrentRenderer = new XYSeriesRenderer();
+        mCurrentRenderer.setColor(color);
+        mRenderer.addSeriesRenderer(mCurrentRenderer);
+
+        mChart = ChartFactory.getLineChartView(context, mDataset, mRenderer);
+        layout.addView(mChart);
+    }
+
+    public void add(int y) {
+        int x = sampleCount++;
+
+        mCurrentSeries.add(x, y);
+
+        mRenderer.setXAxisMax(x);
+        mRenderer.setXAxisMin(x - SCROLLBACK_SIZE + 1);
+
+        if (mCurrentSeries.getItemCount() > SCROLLBACK_SIZE)
+            mCurrentSeries.remove(0);
+
+        // Autoscale
+        double maxY = mCurrentSeries.getMaxY();
+        double minY = mCurrentSeries.getMinY();
+
+        double diffY = maxY - minY;
+        double avgY = (maxY + minY) / 2.0;
+        if (diffY < 4000.0)
+            diffY = 4000.0;
+        maxY = avgY + diffY * 0.625;
+        minY = avgY - diffY * 0.625;
+
+        mRenderer.setYAxisMax(maxY);
+        mRenderer.setYAxisMin(minY);
+
+        mChart.repaint();
+    }
+
+    public void clear() {
+        mCurrentSeries.clear();
+    }
+}
diff --git a/src/com/google/araploxio/Sensor.java b/src/com/google/araploxio/Sensor.java
new file mode 100644
index 0000000..fcd7522
--- /dev/null
+++ b/src/com/google/araploxio/Sensor.java
@@ -0,0 +1,168 @@
+/*
+ * 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.google.araploxio;
+
+import android.content.Context;
+import android.hardware.I2cManager;
+import android.hardware.I2cTransaction;
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class Sensor {
+    // The 7-bit slave address
+    private static final int address = (0x50 >> 1);
+    private static final String bus = "/dev/i2c-4";
+
+    private Context context;
+    private I2cManager i2c;
+    private Handler handler;
+    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+
+    private static I2cTransaction WriteReg(int reg, int b1, int b2, int b3) {
+        return I2cTransaction.newWrite(0x01, reg, b1, b2, b3);
+    }
+
+    private static final I2cTransaction[] setupTxns = {
+        WriteReg(0x00, 0x00, 0x00, 0x08), //AFE4400_CONTROL0
+
+        WriteReg(0x01, 0x00, 0x17, 0xD4), //AFE4400_LED2STC
+        WriteReg(0x02, 0x00, 0x1D, 0xAE), //AFE4400_LED2ENDC
+        WriteReg(0x03, 0x00, 0x17, 0x70), //AFE4400_LED2LEDSTC
+        WriteReg(0x04, 0x00, 0x1D, 0xAF), //AFE4400_LED2LEDENDC
+        WriteReg(0x05, 0x00, 0x00, 0x00), //AFE4400_ALED2STC
+        WriteReg(0x06, 0x00, 0x06, 0x3E), //AFE4400_ALED2ENDC
+
+        WriteReg(0x07, 0x00, 0x08, 0x34), //AFE4400_LED1STC
+        WriteReg(0x08, 0x00, 0x0E, 0x0E), //AFE4400_LED1ENDC
+        WriteReg(0x09, 0x00, 0x07, 0xD0), //AFE4400_LED1LEDSTC
+        WriteReg(0x0A, 0x00, 0x0E, 0x0F), //AFE4400_LED1LEDENDC
+        WriteReg(0x0B, 0x00, 0x0F, 0xA0), //AFE4400_ALED1STC
+        WriteReg(0x0C, 0x00, 0x15, 0xDE), //AFE4400_ALED1ENDC
+
+        WriteReg(0x0D, 0x00, 0x00, 0x02), //AFE4400_LED2CONVST
+        WriteReg(0x0E, 0x00, 0x07, 0xCF), //AFE4400_LED2CONVEND
+        WriteReg(0x0F, 0x00, 0x07, 0xD2), //AFE4400_ALED2CONVST
+        WriteReg(0x10, 0x00, 0x0F, 0x9F), //AFE4400_ALED2CONVEND
+
+        WriteReg(0x11, 0x00, 0x0F, 0xA2), //AFE4400_LED1CONVST
+        WriteReg(0x12, 0x00, 0x17, 0x6F), //AFE4400_LED1CONVEND
+        WriteReg(0x13, 0x00, 0x17, 0x72), //AFE4400_ALED1CONVST
+        WriteReg(0x14, 0x00, 0x1F, 0x3F), //AFE4400_ALED1CONVEND
+
+        WriteReg(0x15, 0x00, 0x00, 0x00), //AFE4400_ADCRSTSTCT0
+        WriteReg(0x16, 0x00, 0x00, 0x00), //AFE4400_ADCRSTENDCT0
+        WriteReg(0x17, 0x00, 0x07, 0xD0), //AFE4400_ADCRSTSTCT1
+        WriteReg(0x18, 0x00, 0x07, 0xD0), //AFE4400_ADCRSTENDCT1
+        WriteReg(0x19, 0x00, 0x0F, 0xA0), //AFE4400_ADCRSTSTCT2
+        WriteReg(0x1A, 0x00, 0x0F, 0xA0), //AFE4400_ADCRSTENDCT2
+        WriteReg(0x1B, 0x00, 0x17, 0x70), //AFE4400_ADCRSTSTCT3
+        WriteReg(0x1C, 0x00, 0x17, 0x70), //AFE4400_ADCRSTENDCT3
+
+        WriteReg(0x1D, 0x00, 0x1F, 0x3F), //AFE4400_PRPCOUNT
+        WriteReg(0x1E, 0x00, 0x01, 0x01), //AFE4400_CONTROL1
+        WriteReg(0x20, 0x00, 0x00, 0x00), //AFE4400_TIAGAIN
+        // Rf=100k Cf=50pF gain=0dB stage2=bypass ambdac=0µA
+        WriteReg(0x21, 0x00, 0x00, 0x3A), //AFE4400_TIA_AMB_GAIN
+        //WriteReg(0x22, 0x01, 0x14, 0x29), //AFE4400_LEDCNTRL
+        WriteReg(0x22, 0x01, 0x4f, 0x4f), //AFE4400_LEDCNTRL
+        WriteReg(0x23, 0x02, 0x01, 0x00), //AFE4400_CONTROL2
+        WriteReg(0x29, 0x00, 0x00, 0x00), //AFE4400_ALARM
+
+        // Setup SPI_READ
+        WriteReg(0x00, 0x00, 0x00, 0x01), //AFE4400_CONTROL0
+    };
+
+    private static final I2cTransaction[] led2Read = {
+        WriteReg(0x2e,                 // LED2-ALED2VAL
+                 0xff, 0xff, 0xff),    // 3 dummy bytes
+        I2cTransaction.newRead(4),
+    };
+    private static final I2cTransaction[] led1Read = {
+        WriteReg(0x2f,                 // LED1-ALED1VAL
+                 0xff, 0xff, 0xff),    // 3 dummy bytes
+        I2cTransaction.newRead(4),
+    };
+
+    private static final I2cTransaction[] resetTxns = {
+        WriteReg(0x00, 0x00, 0x00, 0x08), //AFE4400_CONTROL0
+    };
+
+    public Sensor(Context context, Handler handler) {
+        this.context = context;
+        this.i2c = (I2cManager)context.getSystemService(Context.I2C_SERVICE);
+        this.handler = handler;
+    }
+
+    private I2cTransaction[] execute(I2cTransaction[] txns) {
+        I2cTransaction[] results;
+        try {
+            results = null;
+            for (I2cTransaction txn: txns)
+                results = i2c.performTransactions(bus, address, txn);
+        } catch (IOException e) {
+            throw new RuntimeException("I2C error: " + e);
+        }
+        return results;
+    }
+
+    public void start() {
+        execute(setupTxns);
+        executor.scheduleAtFixedRate(collector, 20, 20, TimeUnit.MILLISECONDS);
+    }
+
+    public void stop() {
+        executor.shutdown();
+        try {
+            executor.awaitTermination(20, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            assert false;
+        }
+        execute(resetTxns);
+    }
+        
+    private final Runnable collector = new Runnable() {
+        public void run() {
+            I2cTransaction[] results;
+            byte[] data;
+            int LED1VAL;
+            int LED2VAL;
+
+            results = execute(led2Read);
+            data = results[0].data;
+            LED2VAL = (((int)data[1] & 0xFF) << 16) |
+                      (((int)data[2] & 0xFF) <<  8) |
+                      (((int)data[3] & 0xFF) <<  0);
+            if (LED2VAL >= 0x800000 && LED2VAL < 0x1000000)
+                LED2VAL -= 0x1000000;
+
+            results = execute(led1Read);
+            data = results[0].data;
+            LED1VAL = (((int)data[1] & 0xFF) << 16) |
+                      (((int)data[2] & 0xFF) <<  8) |
+                      (((int)data[3] & 0xFF) <<  0);
+            if (LED1VAL >= 0x800000 && LED1VAL < 0x1000000)
+                LED1VAL -= 0x1000000;
+
+            handler.obtainMessage(0, LED1VAL, LED2VAL).sendToTarget();
+        }
+    };
+}