Merge from Chromium at DEPS revision r190564

This commit was generated by merge_to_master.py.

Change-Id: Ie93d399ef57a528aa526c28ad0870a992dfc65d3
diff --git a/display/BrailleDisplayProperties.aidl b/display/BrailleDisplayProperties.aidl
new file mode 100644
index 0000000..5a7f410
--- /dev/null
+++ b/display/BrailleDisplayProperties.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+parcelable BrailleDisplayProperties;
diff --git a/display/BrailleDisplayProperties.java b/display/BrailleDisplayProperties.java
new file mode 100644
index 0000000..606476f
--- /dev/null
+++ b/display/BrailleDisplayProperties.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Properties of a braille display such as dimensions and keyboard
+ * configuration.
+ */
+public class BrailleDisplayProperties implements Parcelable {
+    private final int mNumTextCells;
+    private final int mNumStatusCells;
+    private final BrailleKeyBinding[] mKeyBindings;
+    private final Map<String, String> mFriendlyKeyNames;
+
+    public BrailleDisplayProperties(int numTextCells, int numStatusCells,
+            BrailleKeyBinding[] keyBindings,
+            Map<String, String> friendlyKeyNames) {
+        mNumTextCells = numTextCells;
+        mNumStatusCells = numStatusCells;
+        mKeyBindings = keyBindings;
+        mFriendlyKeyNames = friendlyKeyNames;
+    }
+
+    /**
+     * Returns the number of cells on the main display intended for display of
+     * text or other content.
+     */
+    public int getNumTextCells() {
+        return mNumTextCells;
+    }
+
+    /**
+     * Returns the number of status cells that are separated from the main
+     * display.  This value will be {@code 0} for displays without any separate
+     * status cells.
+     */
+    public int getNumStatusCells() {
+        return mNumStatusCells;
+    }
+
+    /**
+     * Returns the list of key bindings for this display.
+     */
+    public BrailleKeyBinding[] getKeyBindings() {
+        return mKeyBindings;
+    }
+
+    /**
+     * Returns an unmodifiable map mapping key names in {@link BrailleKeyBinding}
+     * objects to localized user-friendly key names.
+     */
+    public Map<String, String> getFriendlyKeyNames() {
+        return mFriendlyKeyNames;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+            "BrailleDisplayProperties [numTextCells: %d, numStatusCells: %d, "
+            + "keyBindings: %d]",
+            mNumTextCells, mNumStatusCells, mKeyBindings.length);
+    }
+
+    // For Parcelable support.
+
+    public static final Parcelable.Creator<BrailleDisplayProperties> CREATOR =
+        new Parcelable.Creator<BrailleDisplayProperties>() {
+            @Override
+            public BrailleDisplayProperties createFromParcel(Parcel in) {
+                return new BrailleDisplayProperties(in);
+            }
+
+            @Override
+            public BrailleDisplayProperties[] newArray(int size) {
+                return new BrailleDisplayProperties[size];
+            }
+        };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mNumTextCells);
+        out.writeInt(mNumStatusCells);
+        out.writeTypedArray(mKeyBindings, flags);
+        out.writeInt(mFriendlyKeyNames.size());
+        for (Map.Entry<String, String> entry : mFriendlyKeyNames.entrySet()) {
+            out.writeString(entry.getKey());
+            out.writeString(entry.getValue());
+        }
+    }
+
+    private BrailleDisplayProperties(Parcel in) {
+        mNumTextCells = in.readInt();
+        mNumStatusCells = in.readInt();
+        mKeyBindings = in.createTypedArray(BrailleKeyBinding.CREATOR);
+        int size = in.readInt();
+        Map<String, String> names = new HashMap<String, String>(size);
+        for (int i = 0; i < size; ++i) {
+            names.put(in.readString(), in.readString());
+        }
+        mFriendlyKeyNames = Collections.unmodifiableMap(names);
+    }
+}
diff --git a/display/BrailleInputEvent.aidl b/display/BrailleInputEvent.aidl
new file mode 100644
index 0000000..f64c080
--- /dev/null
+++ b/display/BrailleInputEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+parcelable BrailleInputEvent;
diff --git a/display/BrailleInputEvent.java b/display/BrailleInputEvent.java
new file mode 100644
index 0000000..1c2ffb4
--- /dev/null
+++ b/display/BrailleInputEvent.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import java.util.HashMap;
+
+/**
+ * An input event, originating from a braille display.
+ *
+ * An event contains a command that is a high-level representation of the
+ * key or key combination that was pressed on the display such as a
+ * navigation key or braille keyboard combination.  For some commands, there is
+ * also an integer argument that contains additional information.
+ */
+public class BrailleInputEvent implements Parcelable {
+
+    // Movement commands.
+
+    /** Keyboard command: Used when there is no actual command. */
+    public static final int CMD_NONE = -1;
+
+    /** Keyboard command: Navigate upwards. */
+    public static final int CMD_NAV_LINE_PREVIOUS = 1;
+    /** Keyboard command: Navigate downwards. */
+    public static final int CMD_NAV_LINE_NEXT = 2;
+    /** Keyboard command: Navigate left one item. */
+    public static final int CMD_NAV_ITEM_PREVIOUS = 3;
+    /** Keyboard command: Navigate right one item. */
+    public static final int CMD_NAV_ITEM_NEXT = 4;
+    /** Keyboard command: Navigate one display window to the left. */
+    public static final int CMD_NAV_PAN_LEFT = 5;
+    /** Keyboard command: Navigate one display window to the right. */
+    public static final int CMD_NAV_PAN_RIGHT = 6;
+    /** Keyboard command: Navigate to the top or beginning. */
+    public static final int CMD_NAV_TOP = 7;
+    /** Keyboard command: Navigate to the bottom or end. */
+    public static final int CMD_NAV_BOTTOM = 8;
+
+    // Activation commands.
+
+    /** Keyboard command: Activate the currently selected/focused item. */
+    public static final int CMD_ACTIVATE_CURRENT = 20;
+
+    // Scrolling.
+
+    /** Keyboard command: Scroll backward. */
+    public static final int CMD_SCROLL_BACKWARD = 30;
+    /** Keyboard command: Scroll forward. */
+    public static final int CMD_SCROLL_FORWARD = 31;
+
+    // Selection commands.
+
+    /** Keyboard command: Set the start ot the selection. */
+    public static final int CMD_SELECTION_START = 40;
+    /** Keyboard command: Set the end of the selection. */
+    public static final int CMD_SELECTION_END = 41;
+    /** Keyboard command: Select all content of the current field. */
+    public static final int CMD_SELECTION_SELECT_ALL = 42;
+    /** Keyboard command: Cut the content of the selection. */
+    public static final int CMD_SELECTION_CUT = 43;
+    /** Keyboard command: Copy the current selection. */
+    public static final int CMD_SELECTION_COPY = 44;
+    /**
+     * Keyboard command: Paste the content of the clipboard at the current
+     * insertion point.
+     */
+    public static final int CMD_SELECTION_PASTE = 45;
+
+    /**
+     * Keyboard command: Primary routing key pressed, typically
+     * used to move the insertion point or click/tap on the item
+     * under the key.
+     * The argument is the zero-based position, relative to the first cell
+     * on the display, of the cell that is closed to the key that
+     * was pressed.
+     */
+    public static final int CMD_ROUTE = 50;
+
+    // Braille keyboard input.
+
+    /**
+     * Keyboard command: A key combination was pressed on the braille
+     * keyboard.
+     * The argument contains the dots that were pressed as a bitmask.
+     */
+    public static final int CMD_BRAILLE_KEY = 60;
+
+    // Editing keys.
+
+    /** Keyboard command: Enter key. */
+    public static final int CMD_KEY_ENTER = 70;
+    /** Keyboard command: Delete backward. */
+    public static final int CMD_KEY_DEL = 71;
+    /** Keyboard command: Delete forward. */
+    public static final int CMD_KEY_FORWARD_DEL = 72;
+
+    // Glboal navigation keys.
+
+    /** Keyboard command: Back button. */
+    public static final int CMD_GLOBAL_BACK = 90;
+    /** Keyboard command: Home button. */
+    public static final int CMD_GLOBAL_HOME = 91;
+    /** Keyboard command: Recent apps button. */
+    public static final int CMD_GLOBAL_RECENTS = 92;
+    /** Keyboard command: Show notificaitons. */
+    public static final int CMD_GLOBAL_NOTIFICATIONS = 93;
+
+    // Miscelanous commands.
+
+    /** Keyboard command: Invoke keyboard help. */
+    public static final int CMD_HELP = 100;
+
+    // Meanings of the argument to a command.
+
+    /** This command doesn't have an argument. */
+    public static final int ARGUMENT_NONE = 0;
+    /**
+     * The lower order bits of the arguemnt to this command represent braille
+     * dots.  Dot 1 is represented by the rightmost bit and so on until dot 8,
+     * which is represented by bit 7, counted from the right.
+     */
+    public static final int ARGUMENT_DOTS = 1;
+    /**
+     * The argument represents a 0-based position on the display counted from
+     * the leftmost cell.
+     */
+    public static final int ARGUMENT_POSITION = 2;
+
+    private static final SparseArray<String> CMD_NAMES =
+            new SparseArray<String>();
+    private static final HashMap<String, Integer> NAMES_TO_CMDS
+            = new HashMap<String, Integer>();
+    static {
+        CMD_NAMES.append(CMD_NAV_LINE_PREVIOUS, "CMD_NAV_LINE_PREVIOUS");
+        CMD_NAMES.append(CMD_NAV_LINE_NEXT, "CMD_NAV_LINE_NEXT");
+        CMD_NAMES.append(CMD_NAV_ITEM_PREVIOUS, "CMD_NAV_ITEM_PREVIOUS");
+        CMD_NAMES.append(CMD_NAV_ITEM_NEXT, "CMD_NAV_ITEM_NEXT");
+        CMD_NAMES.append(CMD_NAV_PAN_LEFT, "CMD_NAV_PAN_LEFT");
+        CMD_NAMES.append(CMD_NAV_PAN_RIGHT, "CMD_NAV_PAN_RIGHT");
+        CMD_NAMES.append(CMD_NAV_TOP, "CMD_NAV_TOP");
+        CMD_NAMES.append(CMD_NAV_BOTTOM, "CMD_NAV_BOTTOM");
+        CMD_NAMES.append(CMD_ACTIVATE_CURRENT, "CMD_ACTIVATE_CURRENT");
+        CMD_NAMES.append(CMD_SCROLL_BACKWARD, "CMD_SCROLL_BACKWARD");
+        CMD_NAMES.append(CMD_SCROLL_FORWARD, "CMD_SCROLL_FORWARD");
+        CMD_NAMES.append(CMD_SELECTION_START, "CMD_SELECTION_START");
+        CMD_NAMES.append(CMD_SELECTION_END, "CMD_SELECTION_END");
+        CMD_NAMES.append(CMD_SELECTION_SELECT_ALL, "CMD_SELECTION_SELECT_ALL");
+        CMD_NAMES.append(CMD_SELECTION_CUT, "CMD_SELECTION_CUT");
+        CMD_NAMES.append(CMD_SELECTION_COPY, "CMD_SELECTION_COPY");
+        CMD_NAMES.append(CMD_SELECTION_PASTE, "CMD_SELECTION_PASTE");
+        CMD_NAMES.append(CMD_ROUTE, "CMD_ROUTE");
+        CMD_NAMES.append(CMD_BRAILLE_KEY, "CMD_BRAILLE_KEY");
+        CMD_NAMES.append(CMD_KEY_ENTER, "CMD_KEY_ENTER");
+        CMD_NAMES.append(CMD_KEY_DEL, "CMD_KEY_DEL");
+        CMD_NAMES.append(CMD_KEY_FORWARD_DEL, "CMD_KEY_FORWARD_DEL");
+        CMD_NAMES.append(CMD_GLOBAL_BACK, "CMD_GLOBAL_BACK");
+        CMD_NAMES.append(CMD_GLOBAL_HOME, "CMD_GLOBAL_HOME");
+        CMD_NAMES.append(CMD_GLOBAL_RECENTS, "CMD_GLOBAL_RECENTS");
+        CMD_NAMES.append(CMD_GLOBAL_NOTIFICATIONS, "CMD_GLOBAL_NOTIFICATIONS");
+        CMD_NAMES.append(CMD_HELP, "CMD_HELP");
+        for (int i = 0; i < CMD_NAMES.size(); ++i) {
+            NAMES_TO_CMDS.put(CMD_NAMES.valueAt(i),
+                    CMD_NAMES.keyAt(i));
+        }
+    }
+
+    private final int mCommand;
+    private final int mArgument;
+    private final long mEventTime;
+
+    public BrailleInputEvent(int command, int argument, long eventTime) {
+        mCommand = command;
+        mArgument = argument;
+        mEventTime = eventTime;
+    }
+
+    /**
+     * Returns the keyboard command that this event represents.
+     */
+    public int getCommand() {
+        return mCommand;
+    }
+
+    /**
+     * Returns the command-specific argument of the event, or zero if the
+     * command doesn't have an argument.  See the individual command constants
+     * for more details.
+     */
+    public int getArgument() {
+        return mArgument;
+    }
+
+    /**
+     * Returns the approximate time when this event happened as
+     * returned by {@link android.os.SystemClock#uptimeMillis}.
+     */
+    public long getEventTime() {
+        return mEventTime;
+    }
+
+    /**
+     * Returns a string representation of {@code command}, or the string
+     * {@code (unknown)} if the command is unknown.
+     */
+    public static String commandToString(int command) {
+        String ret = CMD_NAMES.get(command);
+        return ret != null ? ret : "(unknown)";
+    }
+
+    /**
+     * Returns the command corresponding to {@code commandName}, or
+     * {@link #CMD_NONE} if the name doesn't match any existing command.
+     */
+    public static int stringToCommand(String commandName) {
+        Integer command = NAMES_TO_CMDS.get(commandName);
+        if (command == null) {
+            return CMD_NONE;
+        }
+        return command;
+    }
+
+    /**
+     * Returns the type of argument for the given {@code command}.
+     */
+    public static int argumentType(int command) {
+        switch (command) {
+            case CMD_SELECTION_START:
+            case CMD_SELECTION_END:
+            case CMD_ROUTE:
+                return ARGUMENT_POSITION;
+            case CMD_BRAILLE_KEY:
+                return ARGUMENT_DOTS;
+            default:
+                return ARGUMENT_NONE;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("BrailleInputEvent {");
+        sb.append("amd=");
+        sb.append(commandToString(mCommand));
+        sb.append(", arg=");
+        sb.append(mArgument);
+        sb.append("}");
+        return sb.toString();
+    }
+
+    // For Parcelable support.
+
+    public static final Parcelable.Creator<BrailleInputEvent> CREATOR =
+        new Parcelable.Creator<BrailleInputEvent>() {
+            @Override
+            public BrailleInputEvent createFromParcel(Parcel in) {
+                return new BrailleInputEvent(in);
+            }
+
+            @Override
+            public BrailleInputEvent[] newArray(int size) {
+                return new BrailleInputEvent[size];
+            }
+        };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCommand);
+        out.writeInt(mArgument);
+        out.writeLong(mEventTime);
+    }
+
+    private BrailleInputEvent(Parcel in) {
+        mCommand = in.readInt();
+        mArgument = in.readInt();
+        mEventTime = in.readLong();
+    }
+}
diff --git a/display/BrailleKeyBinding.java b/display/BrailleKeyBinding.java
new file mode 100644
index 0000000..92b58d0
--- /dev/null
+++ b/display/BrailleKeyBinding.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a binding between a combination of braille device keys and a
+ * command as declared in {@link BrailleInputEvent}.
+ */
+public class BrailleKeyBinding implements Parcelable {
+    private int mCommand;
+    private String[] mKeyNames;
+
+    public BrailleKeyBinding() {
+    }
+
+    public BrailleKeyBinding(int command, String[] keyNames) {
+        mCommand = command;
+        mKeyNames = keyNames;
+    }
+
+    /**
+     * Sets the command for this binding.
+     */
+    public BrailleKeyBinding setCommand(int command) {
+        mCommand = command;
+        return this;
+    }
+
+    /**
+     * Sets the key names for this binding.
+     */
+    public BrailleKeyBinding setKeyNames(String[] keyNames) {
+        mKeyNames = keyNames;
+        return this;
+    }
+
+    /**
+     * Returns the command for this key binding.
+     * @see {@link BrailleInputEvent}.
+     */
+    public int getCommand() {
+        return mCommand;
+    }
+
+    /**
+     * Returns the list of device-specific keys that, when pressed
+     * at the same time, will yield the command of this key binding.
+     */
+    public String[] getKeyNames() {
+        return mKeyNames;
+    }
+
+    // For Parcelable support.
+
+    public static final Parcelable.Creator<BrailleKeyBinding> CREATOR =
+        new Parcelable.Creator<BrailleKeyBinding>() {
+            @Override
+            public BrailleKeyBinding createFromParcel(Parcel in) {
+                return new BrailleKeyBinding(in);
+            }
+
+            @Override
+            public BrailleKeyBinding[] newArray(int size) {
+                return new BrailleKeyBinding[size];
+            }
+        };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCommand);
+        out.writeStringArray(mKeyNames);
+    }
+
+    private BrailleKeyBinding(Parcel in) {
+        mCommand = in.readInt();
+        mKeyNames = in.createStringArray();
+    }
+}
diff --git a/display/Display.java b/display/Display.java
new file mode 100644
index 0000000..54a57a2
--- /dev/null
+++ b/display/Display.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+import android.os.Message;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A client for the braille display service.
+ */
+public class Display {
+    private static final String LOG_TAG = Display.class.getSimpleName();
+    /** Service name used for connecting to the service. */
+    public static final String ACTION_DISPLAY_SERVICE =
+            "com.googlecode.eyesfree.braille.service.ACTION_DISPLAY_SERVICE";
+
+    /** Initial value, which is never reported to the listener. */
+    private static final int STATE_UNKNOWN = -2;
+    public static final int STATE_ERROR = -1;
+    public static final int STATE_NOT_CONNECTED = 0;
+    public static final int STATE_CONNECTED = 1;
+
+    private final OnConnectionStateChangeListener
+            mConnectionStateChangeListener;
+    private final Context mContext;
+    private final DisplayHandler mHandler;
+    private volatile OnInputEventListener mInputEventListener;
+    private static final Intent mServiceIntent =
+            new Intent(ACTION_DISPLAY_SERVICE);
+    private Connection mConnection;
+    private int currentConnectionState = STATE_UNKNOWN;
+    private BrailleDisplayProperties mDisplayProperties;
+    private ServiceCallback mServiceCallback = new ServiceCallback();
+    /**
+     * Delay before the first rebind attempt on bind error or service
+     * disconnect.
+     */
+    private static final int REBIND_DELAY_MILLIS = 500;
+    private static final int MAX_REBIND_ATTEMPTS = 5;
+    private int mNumFailedBinds = 0;
+
+    /**
+     * A callback interface to get informed about connection state changes.
+     */
+    public interface OnConnectionStateChangeListener {
+        void onConnectionStateChanged(int state);
+    }
+
+    /**
+     * A callback interface for input from the braille display.
+     */
+    public interface OnInputEventListener {
+        void onInputEvent(BrailleInputEvent inputEvent);
+    }
+    
+    /**
+     * Constructs an instance and connects to the braille display service.
+     * The current thread must have an {@link android.os.Looper} associated
+     * with it.  Callbacks from this object will all be executed on the
+     * current thread.  Connection state will be reported to {@code listener).
+     */
+    public Display(Context context, OnConnectionStateChangeListener listener) {
+        this(context, listener, null);
+    }
+
+    /**
+     * Constructs an instance and connects to the braille display service.
+     * Callbacks from this object will all be executed on the thread
+     * associated with {@code handler}.  If {@code handler} is {@code null},
+     * the current thread must have an {@link android.os.Looper} associated
+     * with it, which will then be used to execute callbacks.  Connection
+     * state will be reported to {@code listener).
+     */
+    public Display(Context context, OnConnectionStateChangeListener listener,
+            Handler handler) {
+        mContext = context;
+        mConnectionStateChangeListener = listener;
+        if (handler == null) {
+            mHandler = new DisplayHandler();
+        } else {
+            mHandler = new DisplayHandler(handler);
+        }
+            
+        doBindService();
+    }
+
+    /**
+     * Sets a {@code listener} for input events.  {@code listener} can be
+     * {@code null} to remove a previously set listener.
+     */
+    public void setOnInputEventListener(OnInputEventListener listener) {
+        mInputEventListener = listener;
+    }
+
+    /**
+     * Returns the display properties, or {@code null} if not connected
+     * to a display.
+     */
+    public BrailleDisplayProperties getDisplayProperties() {
+        return mDisplayProperties;
+    }
+
+    /**
+     * Displays a given dots configuration on the braille display.
+     * @param patterns Dots configuration to be displayed.
+     */
+    public void displayDots(byte[] patterns) {
+        IBrailleService localService = getBrailleService();
+        if (localService != null) {
+            try {
+                localService.displayDots(patterns);
+            } catch (RemoteException ex) {
+                Log.e(LOG_TAG, "Error in displayDots", ex);
+            }
+        } else {
+            Log.v(LOG_TAG, "Error in displayDots: service not connected");
+        }
+    }
+
+    /**
+     * Unbinds from the braille display service and deallocates any
+     * resources.  This method should be called when the braille display
+     * is no longer in use by this client.
+     */
+    public void shutdown() {
+        doUnbindService();
+    }
+
+    // NOTE: The methods in this class will be executed in the main
+    // application thread.
+    private class Connection implements ServiceConnection {
+        private volatile IBrailleService mService;
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder binder) {
+            Log.i(LOG_TAG, "Connected to braille service");
+            IBrailleService localService =
+                IBrailleService.Stub.asInterface(binder);
+            try {
+                localService.registerCallback(mServiceCallback);
+                mService = localService;
+                synchronized (mHandler) {
+                    mNumFailedBinds = 0;
+                }
+            } catch (RemoteException e) {
+                // In this case the service has crashed before we could even do
+                // anything with it.
+                Log.e(LOG_TAG, "Failed to register callback on service", e);
+                // We should get a disconnected call and the rebind
+                // and failure reporting happens in that handler.
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+            Log.e(LOG_TAG, "Disconnected from braille service");
+            // Report display disconnected for now, this will turn into a
+            // connected state or error state depending on how the retrying
+            // goes.
+            mHandler.reportConnectionState(STATE_NOT_CONNECTED, null);
+            mHandler.scheduleRebind();
+        }
+    }
+
+    // NOTE: The methods of this class will be executed in the IPC
+    // thread pool and not on the main application thread.
+    private class ServiceCallback extends IBrailleServiceCallback.Stub {
+        @Override
+        public void onDisplayConnected(
+            BrailleDisplayProperties displayProperties) {
+            mHandler.reportConnectionState(STATE_CONNECTED, displayProperties);
+        }
+
+        @Override
+        public void onDisplayDisconnected() {
+            mHandler.reportConnectionState(STATE_NOT_CONNECTED, null);
+        }
+
+        @Override
+        public void onInput(BrailleInputEvent inputEvent) {
+            mHandler.reportInputEvent(inputEvent);
+        }
+    }
+
+    private void doBindService() {
+        Connection localConnection = new Connection();
+        if (!mContext.bindService(mServiceIntent, localConnection,
+                Context.BIND_AUTO_CREATE)) {
+            Log.e(LOG_TAG, "Failed to bind Service");
+            mHandler.scheduleRebind();
+            return;
+        }
+        mConnection = localConnection;
+        Log.i(LOG_TAG, "Bound to braille service");
+    }
+
+    private void doUnbindService() {
+        IBrailleService localService = getBrailleService();
+        if (localService != null) {
+            try {
+                localService.unregisterCallback(mServiceCallback);
+            } catch (RemoteException e) {
+                // Nothing to do if the service can't be reached.
+            }
+        }
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    private IBrailleService getBrailleService() {
+        Connection localConnection = mConnection;
+        if (localConnection != null) {
+            return localConnection.mService;
+        }
+        return null;
+    }
+
+    private class DisplayHandler extends Handler {
+        private static final int MSG_REPORT_CONNECTION_STATE = 1;
+        private static final int MSG_REPORT_INPUT_EVENT = 2;
+        private static final int MSG_REBIND_SERVICE = 3;
+
+        public DisplayHandler() {
+        }
+
+        public DisplayHandler(Handler handler) {
+            super(handler.getLooper());
+        }
+
+        public void reportConnectionState(final int newState,
+                final BrailleDisplayProperties displayProperties) {
+            obtainMessage(MSG_REPORT_CONNECTION_STATE, newState, 0,
+                    displayProperties)
+                    .sendToTarget();
+        }
+
+        public void reportInputEvent(BrailleInputEvent event) {
+            obtainMessage(MSG_REPORT_INPUT_EVENT, event).sendToTarget();
+        }
+
+        public void scheduleRebind() {
+            synchronized (this) {
+                if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+                    int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+                    sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+                    ++mNumFailedBinds;
+                    Log.w(LOG_TAG, String.format(
+                        "Will rebind to braille service in %d ms.", delay));
+                } else {
+                    reportConnectionState(STATE_ERROR, null);
+                }
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REPORT_CONNECTION_STATE:
+                    handleReportConnectionState(msg.arg1,
+                            (BrailleDisplayProperties) msg.obj);
+                    break;
+                case MSG_REPORT_INPUT_EVENT:
+                    handleReportInputEvent((BrailleInputEvent) msg.obj);
+                    break;
+                case MSG_REBIND_SERVICE:
+                    handleRebindService();
+                    break;
+            }
+        }
+
+        private void handleReportConnectionState(int newState,
+                BrailleDisplayProperties displayProperties) {
+            mDisplayProperties = displayProperties;
+            if (newState != currentConnectionState
+                    && mConnectionStateChangeListener != null) {
+                mConnectionStateChangeListener.onConnectionStateChanged(
+                    newState);
+            }
+            currentConnectionState = newState;
+        }
+
+        private void handleReportInputEvent(BrailleInputEvent event) {
+            OnInputEventListener localListener = mInputEventListener;
+            if (localListener != null) {
+                localListener.onInputEvent(event);
+            }
+        }
+
+        private void handleRebindService() {
+            if (mConnection != null) {
+                doUnbindService();
+            }
+            doBindService();
+        }
+    }
+}
diff --git a/display/IBrailleService.aidl b/display/IBrailleService.aidl
new file mode 100644
index 0000000..2b478bb
--- /dev/null
+++ b/display/IBrailleService.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+import com.googlecode.eyesfree.braille.display.IBrailleServiceCallback;
+
+/**
+ * Interface for clients to talk to the braille display service.
+ */
+interface IBrailleService {
+    /**
+     * Register a callback for the {@code callingApp} which will receive
+     * certain braille display related events.
+     */
+    boolean registerCallback(in IBrailleServiceCallback callback);
+
+    /**
+     * Unregister a previously registered callback for the {@code callingApp}.
+     */
+    oneway void unregisterCallback(in IBrailleServiceCallback callback);
+
+    /**
+     * Updates the main cells of the connected braille display
+     * with a given dot {@code pattern}.
+     *
+     * @return {@code true} on success and {@code false} otherwise.
+     */
+    void displayDots(in byte[] patterns);
+}
diff --git a/display/IBrailleServiceCallback.aidl b/display/IBrailleServiceCallback.aidl
new file mode 100644
index 0000000..545d1ad
--- /dev/null
+++ b/display/IBrailleServiceCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.display;
+
+import com.googlecode.eyesfree.braille.display.BrailleDisplayProperties;
+import com.googlecode.eyesfree.braille.display.BrailleInputEvent;
+
+/**
+ * Callback interface that a braille display client can expose to
+ * get information about various braille display events.
+ */
+interface IBrailleServiceCallback {
+    void onDisplayConnected(in BrailleDisplayProperties displayProperties);
+    void onDisplayDisconnected();
+    void onInput(in BrailleInputEvent inputEvent);
+}
diff --git a/selfbraille/ISelfBrailleService.aidl b/selfbraille/ISelfBrailleService.aidl
new file mode 100644
index 0000000..770c283
--- /dev/null
+++ b/selfbraille/ISelfBrailleService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.selfbraille;
+
+import com.googlecode.eyesfree.braille.selfbraille.WriteData;
+
+/**
+ * Interface for a client to control braille output for a part of the
+ * accessibility node tree.
+ */
+interface ISelfBrailleService {
+    void write(IBinder clientToken, in WriteData writeData);
+    oneway void disconnect(IBinder clientToken);
+}
diff --git a/selfbraille/SelfBrailleClient.java b/selfbraille/SelfBrailleClient.java
new file mode 100644
index 0000000..e4a363a
--- /dev/null
+++ b/selfbraille/SelfBrailleClient.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.selfbraille;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Client-side interface to the self brailling interface.
+ *
+ * Threading: Instances of this object should be created and shut down
+ * in a thread with a {@link Looper} associated with it.  Other methods may
+ * be called on any thread.
+ */
+public class SelfBrailleClient {
+    private static final String LOG_TAG =
+            SelfBrailleClient.class.getSimpleName();
+    private static final String ACTION_SELF_BRAILLE_SERVICE =
+            "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE";
+    private static final String BRAILLE_BACK_PACKAGE =
+            "com.googlecode.eyesfree.brailleback";
+    private static final Intent mServiceIntent =
+            new Intent(ACTION_SELF_BRAILLE_SERVICE)
+            .setPackage(BRAILLE_BACK_PACKAGE);
+    /**
+     * SHA-1 hash value of the Eyes-Free release key certificate, used to sign
+     * BrailleBack.  It was generated from the keystore with:
+     * $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \
+     *   > cert
+     * $ keytool -printcert -file cert
+     */
+    // The typecasts are to silence a compiler warning about loss of precision
+    private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] {
+        (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D,
+        (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4,
+        (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B,
+        (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76,
+        (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61
+    };
+    /**
+     * Delay before the first rebind attempt on bind error or service
+     * disconnect.
+     */
+    private static final int REBIND_DELAY_MILLIS = 500;
+    private static final int MAX_REBIND_ATTEMPTS = 5;
+
+    private final Binder mIdentity = new Binder();
+    private final Context mContext;
+    private final boolean mAllowDebugService;
+    private final SelfBrailleHandler mHandler = new SelfBrailleHandler();
+    private boolean mShutdown = false;
+
+    /**
+     * Written in handler thread, read in any thread calling methods on the
+     * object.
+     */
+    private volatile Connection mConnection;
+    /** Protected by synchronizing on mHandler. */
+    private int mNumFailedBinds = 0;
+
+    /**
+     * Constructs an instance of this class.  {@code context} is used to bind
+     * to the self braille service.  The current thread must have a Looper
+     * associated with it.  If {@code allowDebugService} is true, this instance
+     * will connect to a BrailleBack service without requiring it to be signed
+     * by the release key used to sign BrailleBack.
+     */
+    public SelfBrailleClient(Context context, boolean allowDebugService) {
+        mContext = context;
+        mAllowDebugService = allowDebugService;
+        doBindService();
+    }
+
+    /**
+     * Shuts this instance down, deallocating any global resources it is using.
+     * This method must be called on the same thread that created this object.
+     */
+    public void shutdown() {
+        mShutdown = true;
+        doUnbindService();
+    }
+
+    public void write(WriteData writeData) {
+        writeData.validate();
+        ISelfBrailleService localService = getSelfBrailleService();
+        if (localService != null) {
+            try {
+                localService.write(mIdentity, writeData);
+            } catch (RemoteException ex) {
+                Log.e(LOG_TAG, "Self braille write failed", ex);
+            }
+        }
+    }
+
+    private void doBindService() {
+        Connection localConnection = new Connection();
+        if (!mContext.bindService(mServiceIntent, localConnection,
+                Context.BIND_AUTO_CREATE)) {
+            Log.e(LOG_TAG, "Failed to bind to service");
+            mHandler.scheduleRebind();
+            return;
+        }
+        mConnection = localConnection;
+        Log.i(LOG_TAG, "Bound to self braille service");
+    }
+
+    private void doUnbindService() {
+        if (mConnection != null) {
+            ISelfBrailleService localService = getSelfBrailleService();
+            if (localService != null) {
+                try {
+                    localService.disconnect(mIdentity);
+                } catch (RemoteException ex) {
+                    // Nothing to do.
+                }
+            }
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    private ISelfBrailleService getSelfBrailleService() {
+        Connection localConnection = mConnection;
+        if (localConnection != null) {
+            return localConnection.mService;
+        }
+        return null;
+    }
+
+    private boolean verifyPackage() {
+        PackageManager pm = mContext.getPackageManager();
+        PackageInfo pi;
+        try {
+            pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE,
+                    PackageManager.GET_SIGNATURES);
+        } catch (PackageManager.NameNotFoundException ex) {
+            Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE,
+                    ex);
+            return false;
+        }
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException ex) {
+            Log.e(LOG_TAG, "SHA-1 not supported", ex);
+            return false;
+        }
+        // Check if any of the certificates match our hash.
+        for (Signature signature : pi.signatures) {
+            digest.update(signature.toByteArray());
+            if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) {
+                return true;
+            }
+            digest.reset();
+        }
+        if (mAllowDebugService) {
+            Log.w(LOG_TAG, String.format(
+                "*** %s connected to BrailleBack with invalid (debug?) "
+                + "signature ***",
+                mContext.getPackageName()));
+            return true;
+        }
+        return false;
+    }
+    private class Connection implements ServiceConnection {
+        // Read in application threads, written in main thread.
+        private volatile ISelfBrailleService mService;
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder binder) {
+            if (!verifyPackage()) {
+                Log.w(LOG_TAG, String.format("Service certificate mismatch "
+                                + "for %s, dropping connection",
+                                BRAILLE_BACK_PACKAGE));
+                mHandler.unbindService();
+                return;
+            }
+            Log.i(LOG_TAG, "Connected to self braille service");
+            mService = ISelfBrailleService.Stub.asInterface(binder);
+            synchronized (mHandler) {
+                mNumFailedBinds = 0;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            Log.e(LOG_TAG, "Disconnected from self braille service");
+            mService = null;
+            // Retry by rebinding.
+            mHandler.scheduleRebind();
+        }
+    }
+
+    private class SelfBrailleHandler extends Handler {
+        private static final int MSG_REBIND_SERVICE = 1;
+        private static final int MSG_UNBIND_SERVICE = 2;
+
+        public void scheduleRebind() {
+            synchronized (this) {
+                if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+                    int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+                    sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+                    ++mNumFailedBinds;
+                }
+            }
+        }
+
+        public void unbindService() {
+            sendEmptyMessage(MSG_UNBIND_SERVICE);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REBIND_SERVICE:
+                    handleRebindService();
+                    break;
+                case MSG_UNBIND_SERVICE:
+                    handleUnbindService();
+                    break;
+            }
+        }
+
+        private void handleRebindService() {
+            if (mShutdown) {
+                return;
+            }
+            if (mConnection != null) {
+                doUnbindService();
+            }
+            doBindService();
+        }
+
+        private void handleUnbindService() {
+            doUnbindService();
+        }
+    }
+}
diff --git a/selfbraille/WriteData.aidl b/selfbraille/WriteData.aidl
new file mode 100644
index 0000000..b02ec85
--- /dev/null
+++ b/selfbraille/WriteData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.selfbraille;
+
+parcelable WriteData;
diff --git a/selfbraille/WriteData.java b/selfbraille/WriteData.java
new file mode 100644
index 0000000..3c16502
--- /dev/null
+++ b/selfbraille/WriteData.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.selfbraille;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Represents what should be shown on the braille display for a
+ * part of the accessibility node tree.
+ */
+public class WriteData implements Parcelable {
+
+    private static final String PROP_SELECTION_START = "selectionStart";
+    private static final String PROP_SELECTION_END = "selectionEnd";
+
+    private AccessibilityNodeInfo mAccessibilityNodeInfo;
+    private CharSequence mText;
+    private Bundle mProperties = Bundle.EMPTY;
+
+    /**
+     * Returns a new {@link WriteData} instance for the given {@code view}.
+     */
+    public static WriteData forView(View view) {
+        AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
+        WriteData writeData = new WriteData();
+        writeData.mAccessibilityNodeInfo = node;
+        return writeData;
+    }
+
+    public AccessibilityNodeInfo getAccessibilityNodeInfo() {
+        return mAccessibilityNodeInfo;
+    }
+
+    /**
+     * Sets the text to be displayed when the accessibility node associated
+     * with this instance has focus.  If this method is not called (or
+     * {@code text} is {@code null}), this client relinquishes control over
+     * this node.
+     */
+    public WriteData setText(CharSequence text) {
+        mText = text;
+        return this;
+    }
+
+    public CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Sets the start position in the text of a text selection or cursor that
+     * should be marked on the display.  A negative value (the default) means
+     * no selection will be added.
+     */
+    public WriteData setSelectionStart(int v) {
+        writableProperties().putInt(PROP_SELECTION_START, v);
+        return this;
+    }
+
+    /**
+     * @see {@link #setSelectionStart}.
+     */
+    public int getSelectionStart() {
+        return mProperties.getInt(PROP_SELECTION_START, -1);
+    }
+
+    /**
+     * Sets the end of the text selection to be marked on the display.  This
+     * value should only be non-negative if the selection start is
+     * non-negative.  If this value is <= the selection start, the selection
+     * is a cursor.  Otherwise, the selection covers the range from
+     * start(inclusive) to end (exclusive).
+     *
+     * @see {@link android.text.Selection}.
+     */
+    public WriteData setSelectionEnd(int v) {
+        writableProperties().putInt(PROP_SELECTION_END, v);
+        return this;
+    }
+
+    /**
+     * @see {@link #setSelectionEnd}.
+     */
+    public int getSelectionEnd() {
+        return mProperties.getInt(PROP_SELECTION_END, -1);
+    }
+
+    private Bundle writableProperties() {
+        if (mProperties == Bundle.EMPTY) {
+            mProperties = new Bundle();
+        }
+        return mProperties;
+    }
+
+    /**
+     * Checks constraints on the fields that must be satisfied before sending
+     * this instance to the self braille service.
+     * @throws IllegalStateException
+     */
+    public void validate() throws IllegalStateException {
+        if (mAccessibilityNodeInfo == null) {
+            throw new IllegalStateException(
+                "Accessibility node info can't be null");
+        }
+        int selectionStart = getSelectionStart();
+        int selectionEnd = getSelectionEnd();
+        if (mText == null) {
+            if (selectionStart > 0 || selectionEnd > 0) {
+                throw new IllegalStateException(
+                    "Selection can't be set without text");
+            }
+        } else {
+            if (selectionStart < 0 && selectionEnd >= 0) {
+                throw new IllegalStateException(
+                    "Selection end without start");
+            }
+            int textLength = mText.length();
+            if (selectionStart > textLength || selectionEnd > textLength) {
+                throw new IllegalStateException("Selection out of bounds");
+            }
+        }
+    }
+
+    // For Parcelable support.
+
+    public static final Parcelable.Creator<WriteData> CREATOR =
+        new Parcelable.Creator<WriteData>() {
+            @Override
+            public WriteData createFromParcel(Parcel in) {
+                return new WriteData(in);
+            }
+
+            @Override
+            public WriteData[] newArray(int size) {
+                return new WriteData[size];
+            }
+        };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <strong>Note:</strong> The {@link AccessibilityNodeInfo} will be
+     * recycled by this method, don't try to use this more than once.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        mAccessibilityNodeInfo.writeToParcel(out, flags);
+        // The above call recycles the node, so make sure we don't use it
+        // anymore.
+        mAccessibilityNodeInfo = null;
+        out.writeString(mText.toString());
+        out.writeBundle(mProperties);
+    }
+
+    private WriteData() {
+    }
+
+    private WriteData(Parcel in) {
+        mAccessibilityNodeInfo =
+                AccessibilityNodeInfo.CREATOR.createFromParcel(in);
+        mText = in.readString();
+        mProperties = in.readBundle();
+    }
+}
diff --git a/translate/BrailleTranslator.java b/translate/BrailleTranslator.java
new file mode 100644
index 0000000..e7ee9cb
--- /dev/null
+++ b/translate/BrailleTranslator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.translate;
+
+/**
+ * Translates from text to braille and the other way according to a
+ * particular translation table.
+ */
+public interface BrailleTranslator {
+    /**
+     * Translates a string into the corresponding dot patterns and returns the
+     * resulting byte array.  Returns {@code null} on error.
+     */
+    byte[] translate(String text);
+
+    /**
+     * Translates the braille {@code cells} into the corresponding text, which
+     * is returned.  Returns {@code null} on error.
+     */
+    String backTranslate(byte[] cells);
+}
diff --git a/translate/ITranslatorService.aidl b/translate/ITranslatorService.aidl
new file mode 100644
index 0000000..1ccab87
--- /dev/null
+++ b/translate/ITranslatorService.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.translate;
+
+import com.googlecode.eyesfree.braille.translate.ITranslatorServiceCallback;
+
+interface ITranslatorService {
+    /**
+     * Sets a callback to be called when the service is ready to translate.
+     * Using any of the other methods in this interface before the
+     * callback is called with a successful status will return
+     * failure.
+     */
+    void setCallback(ITranslatorServiceCallback callback);
+
+    /**
+     * Makes sure that the given table string is valid and that the
+     * table compiles.
+     */
+    boolean checkTable(String tableName);
+
+    /**
+     * Translates text into braille according to the give tableName.
+     * Returns null on fatal translation errors.
+     */
+    byte[] translate(String text, String tableName);
+
+    /**
+     * Translates braille cells into text according to the given table
+     * name.  Returns null on fatal translation errors.
+     */
+    String backTranslate(in byte[] cells, String tableName);
+}
diff --git a/translate/ITranslatorServiceCallback.aidl b/translate/ITranslatorServiceCallback.aidl
new file mode 100644
index 0000000..91c74cb
--- /dev/null
+++ b/translate/ITranslatorServiceCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.translate;
+
+oneway interface ITranslatorServiceCallback {
+    void onInit(int status);
+}
diff --git a/translate/TranslatorManager.java b/translate/TranslatorManager.java
new file mode 100644
index 0000000..841a041
--- /dev/null
+++ b/translate/TranslatorManager.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 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.googlecode.eyesfree.braille.translate;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Client-side interface to the central braille translator service.
+ *
+ * This class can be used to retrieve {@link BrailleTranslator} instances for
+ * performing translation between text and braille cells.
+ *
+ * Typically, an instance of this class is created at application
+ * initialization time and destroyed using the {@link destroy()} method when
+ * the application is about to be destroyed.  It is recommended that the
+ * instance is destroyed and recreated if braille translation is not going to
+ * be need for a long period of time.
+ *
+ * Threading:<br>
+ * The object must be destroyed on the same thread it was created.
+ * Other methods may be called from any thread.
+ */
+public class TranslatorManager {
+    private static final String LOG_TAG =
+            TranslatorManager.class.getSimpleName();
+    private static final String ACTION_TRANSLATOR_SERVICE =
+            "com.googlecode.eyesfree.braille.service.ACTION_TRANSLATOR_SERVICE";
+    private static final Intent mServiceIntent =
+            new Intent(ACTION_TRANSLATOR_SERVICE);
+    /**
+     * Delay before the first rebind attempt on bind error or service
+     * disconnect.
+     */
+    private static final int REBIND_DELAY_MILLIS = 500;
+    private static final int MAX_REBIND_ATTEMPTS = 5;
+    public static final int ERROR = -1;
+    public static final int SUCCESS = 0;
+
+    /**
+     * A callback interface to get notified when the translation
+     * manager is ready to be used, or an error occurred during
+     * initialization.
+     */
+    public interface OnInitListener {
+        /**
+         * Called exactly once when it has been determined that the
+         * translation service is either ready to be used ({@code SUCCESS})
+         * or the service is not available {@code ERROR}.
+         */
+        public void onInit(int status);
+    }
+
+    private final Context mContext;
+    private final TranslatorManagerHandler mHandler =
+            new TranslatorManagerHandler();
+    private final ServiceCallback mServiceCallback = new ServiceCallback();
+
+    private OnInitListener mOnInitListener;
+    private Connection mConnection;
+    private int mNumFailedBinds = 0;
+
+    /**
+     * Constructs an instance.  {@code context} is used to bind to the
+     * translator service.  The other methods of this class should not be
+     * called (they will fail) until {@code onInitListener.onInit()}
+     * is called.
+     */
+    public TranslatorManager(Context context, OnInitListener onInitListener) {
+        mContext = context;
+        mOnInitListener = onInitListener;
+        doBindService();
+    }
+
+    /**
+     * Destroys this instance, deallocating any global resources it is using.
+     * Any {@link BrailleTranslator} objects that were created using this
+     * object are invalid after this call.
+     */
+    public void destroy() {
+        doUnbindService();
+        mHandler.destroy();
+    }
+
+    /**
+     * Returns a new {@link BrailleTranslator} for the translation
+     * table specified by {@code tableName}.
+     */
+    // TODO: Document how to discover valid table names.
+    public BrailleTranslator getTranslator(String tableName) {
+        ITranslatorService localService = getTranslatorService();
+        if (localService != null) {
+            try {
+                if (localService.checkTable(tableName)) {
+                    return new BrailleTranslatorImpl(tableName);
+                }
+            } catch (RemoteException ex) {
+                Log.e(LOG_TAG, "Error in getTranslator", ex);
+            }
+        }
+        return null;
+    }
+
+    private void doBindService() {
+        Connection localConnection = new Connection();
+        if (!mContext.bindService(mServiceIntent, localConnection,
+                Context.BIND_AUTO_CREATE)) {
+            Log.e(LOG_TAG, "Failed to bind to service");
+            mHandler.scheduleRebind();
+            return;
+        }
+        mConnection = localConnection;
+        Log.i(LOG_TAG, "Bound to translator service");
+    }
+
+    private void doUnbindService() {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    private ITranslatorService getTranslatorService() {
+        Connection localConnection = mConnection;
+        if (localConnection != null) {
+            return localConnection.mService;
+        }
+        return null;
+    }
+
+    private class Connection implements ServiceConnection {
+        // Read in application threads, written in main thread.
+        private volatile ITranslatorService mService;
+
+        @Override
+        public void onServiceConnected(ComponentName className,
+                IBinder binder) {
+            Log.i(LOG_TAG, "Connected to translation service");
+            ITranslatorService localService =
+                    ITranslatorService.Stub.asInterface(binder);
+            try {
+                localService.setCallback(mServiceCallback);
+                mService = localService;
+                synchronized (mHandler) {
+                    mNumFailedBinds = 0;
+                }
+            } catch (RemoteException ex) {
+                // Service went away, rely on disconnect handler to
+                // schedule a rebind.
+                Log.e(LOG_TAG, "Error when setting callback", ex);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            Log.e(LOG_TAG, "Disconnected from translator service");
+            mService = null;
+            // Retry by rebinding, and finally call the onInit if aplicable.
+            mHandler.scheduleRebind();
+        }
+    }
+
+    private class BrailleTranslatorImpl implements BrailleTranslator {
+        private final String mTable;
+
+        public BrailleTranslatorImpl(String table) {
+            mTable = table;
+        }
+
+        @Override
+        public byte[] translate(String text) {
+            ITranslatorService localService = getTranslatorService();
+            if (localService != null) {
+                try {
+                    return localService.translate(text, mTable);
+                } catch (RemoteException ex) {
+                    Log.e(LOG_TAG, "Error in translate", ex);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public String backTranslate(byte[] cells) {
+            ITranslatorService localService = getTranslatorService();
+            if (localService != null) {
+                try {
+                    return localService.backTranslate(cells, mTable);
+                } catch (RemoteException ex) {
+                    Log.e(LOG_TAG, "Error in backTranslate", ex);
+                }
+            }
+            return null;
+        }
+    }
+
+    private class ServiceCallback extends ITranslatorServiceCallback.Stub {
+        @Override
+        public void onInit(int status) {
+            mHandler.onInit(status);
+        }
+    }
+
+    private class TranslatorManagerHandler extends Handler {
+        private static final int MSG_ON_INIT = 1;
+        private static final int MSG_REBIND_SERVICE = 2;
+
+        public void onInit(int status) {
+            obtainMessage(MSG_ON_INIT, status, 0).sendToTarget();
+        }
+
+        public void destroy() {
+            mOnInitListener = null;
+            // Cacnel outstanding messages, most importantly
+            // scheduled rebinds.
+            removeCallbacksAndMessages(null);
+        }
+
+        public void scheduleRebind() {
+            synchronized (this) {
+                if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) {
+                    int delay = REBIND_DELAY_MILLIS << mNumFailedBinds;
+                    sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay);
+                    ++mNumFailedBinds;
+                } else {
+                    onInit(ERROR);
+                }
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_INIT:
+                    handleOnInit(msg.arg1);
+                    break;
+                case MSG_REBIND_SERVICE:
+                    handleRebindService();
+                    break;
+            }
+        }
+
+        private void handleOnInit(int status) {
+            if (mOnInitListener != null) {
+                mOnInitListener.onInit(status);
+                mOnInitListener = null;
+            }
+        }
+
+        private void handleRebindService() {
+            if (mConnection != null) {
+                doUnbindService();
+            }
+            doBindService();
+        }
+    }
+}