diff options
author | plundblad@google.com <plundblad@google.com@584082c0-ab3a-11dd-9ddb-6f86aeb5eef6> | 2012-07-03 21:38:44 +0000 |
---|---|---|
committer | plundblad@google.com <plundblad@google.com@584082c0-ab3a-11dd-9ddb-6f86aeb5eef6> | 2012-07-03 21:38:44 +0000 |
commit | 344caf0bfb20161349dbf2f658a7f096a08ea769 (patch) | |
tree | 3eb1c50e0b9222f8e544f5b59e443a8c84a204d5 | |
download | braille-344caf0bfb20161349dbf2f658a7f096a08ea769.tar.gz |
Add BrailleBack.
git-svn-id: https://eyes-free.googlecode.com/svn/trunk/braille/client/src/com/googlecode/eyesfree/braille@781 584082c0-ab3a-11dd-9ddb-6f86aeb5eef6
-rw-r--r-- | display/BrailleDisplayProperties.aidl | 19 | ||||
-rw-r--r-- | display/BrailleDisplayProperties.java | 102 | ||||
-rw-r--r-- | display/BrailleInputEvent.aidl | 19 | ||||
-rw-r--r-- | display/BrailleInputEvent.java | 300 | ||||
-rw-r--r-- | display/BrailleKeyBinding.java | 100 | ||||
-rw-r--r-- | display/Display.java | 321 | ||||
-rw-r--r-- | display/IBrailleService.aidl | 43 | ||||
-rw-r--r-- | display/IBrailleServiceCallback.aidl | 30 | ||||
-rw-r--r-- | translate/BrailleTranslator.java | 35 | ||||
-rw-r--r-- | translate/ITranslatorService.aidl | 47 | ||||
-rw-r--r-- | translate/ITranslatorServiceCallback.aidl | 21 | ||||
-rw-r--r-- | translate/TranslatorManager.java | 278 |
12 files changed, 1315 insertions, 0 deletions
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..f61bad0 --- /dev/null +++ b/display/BrailleDisplayProperties.java @@ -0,0 +1,102 @@ +/* + * 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; + +/** + * 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; + + public BrailleDisplayProperties(int numTextCells, int numStatusCells, + BrailleKeyBinding[] keyBindings) { + mNumTextCells = numTextCells; + mNumStatusCells = numStatusCells; + mKeyBindings = keyBindings; + } + + /** + * 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; + } + + @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); + } + + private BrailleDisplayProperties(Parcel in) { + mNumTextCells = in.readInt(); + mNumStatusCells = in.readInt(); + mKeyBindings = in.createTypedArray(BrailleKeyBinding.CREATOR); + } +} 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/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(); + } + } +} |