From 344caf0bfb20161349dbf2f658a7f096a08ea769 Mon Sep 17 00:00:00 2001 From: "plundblad@google.com" Date: Tue, 3 Jul 2012 21:38:44 +0000 Subject: 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 --- display/BrailleDisplayProperties.aidl | 19 ++ display/BrailleDisplayProperties.java | 102 ++++++++++ display/BrailleInputEvent.aidl | 19 ++ display/BrailleInputEvent.java | 300 ++++++++++++++++++++++++++++ display/BrailleKeyBinding.java | 100 ++++++++++ display/Display.java | 321 ++++++++++++++++++++++++++++++ display/IBrailleService.aidl | 43 ++++ display/IBrailleServiceCallback.aidl | 30 +++ translate/BrailleTranslator.java | 35 ++++ translate/ITranslatorService.aidl | 47 +++++ translate/ITranslatorServiceCallback.aidl | 21 ++ translate/TranslatorManager.java | 278 ++++++++++++++++++++++++++ 12 files changed, 1315 insertions(+) create mode 100644 display/BrailleDisplayProperties.aidl create mode 100644 display/BrailleDisplayProperties.java create mode 100644 display/BrailleInputEvent.aidl create mode 100644 display/BrailleInputEvent.java create mode 100644 display/BrailleKeyBinding.java create mode 100644 display/Display.java create mode 100644 display/IBrailleService.aidl create mode 100644 display/IBrailleServiceCallback.aidl create mode 100644 translate/BrailleTranslator.java create mode 100644 translate/ITranslatorService.aidl create mode 100644 translate/ITranslatorServiceCallback.aidl create mode 100644 translate/TranslatorManager.java 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 CREATOR = + new Parcelable.Creator() { + @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 CMD_NAMES = + new SparseArray(); + private static final HashMap NAMES_TO_CMDS + = new HashMap(); + 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 CREATOR = + new Parcelable.Creator() { + @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 CREATOR = + new Parcelable.Creator() { + @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:
+ * 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(); + } + } +} -- cgit v1.2.3 From 4facfd4e7cac6623d53909b14d6694c6751e3254 Mon Sep 17 00:00:00 2001 From: "plundblad@google.com" Date: Mon, 27 Aug 2012 23:37:20 +0000 Subject: Push BrailleBack 0.9_r4. git-svn-id: https://eyes-free.googlecode.com/svn/trunk/braille/client/src/com/googlecode/eyesfree/braille@788 584082c0-ab3a-11dd-9ddb-6f86aeb5eef6 --- display/BrailleDisplayProperties.java | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/display/BrailleDisplayProperties.java b/display/BrailleDisplayProperties.java index f61bad0..606476f 100644 --- a/display/BrailleDisplayProperties.java +++ b/display/BrailleDisplayProperties.java @@ -19,6 +19,10 @@ 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. @@ -27,12 +31,15 @@ public class BrailleDisplayProperties implements Parcelable { private final int mNumTextCells; private final int mNumStatusCells; private final BrailleKeyBinding[] mKeyBindings; + private final Map mFriendlyKeyNames; public BrailleDisplayProperties(int numTextCells, int numStatusCells, - BrailleKeyBinding[] keyBindings) { + BrailleKeyBinding[] keyBindings, + Map friendlyKeyNames) { mNumTextCells = numTextCells; mNumStatusCells = numStatusCells; mKeyBindings = keyBindings; + mFriendlyKeyNames = friendlyKeyNames; } /** @@ -59,6 +66,14 @@ public class BrailleDisplayProperties implements Parcelable { return mKeyBindings; } + /** + * Returns an unmodifiable map mapping key names in {@link BrailleKeyBinding} + * objects to localized user-friendly key names. + */ + public Map getFriendlyKeyNames() { + return mFriendlyKeyNames; + } + @Override public String toString() { return String.format( @@ -92,11 +107,22 @@ public class BrailleDisplayProperties implements Parcelable { out.writeInt(mNumTextCells); out.writeInt(mNumStatusCells); out.writeTypedArray(mKeyBindings, flags); + out.writeInt(mFriendlyKeyNames.size()); + for (Map.Entry 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 names = new HashMap(size); + for (int i = 0; i < size; ++i) { + names.put(in.readString(), in.readString()); + } + mFriendlyKeyNames = Collections.unmodifiableMap(names); } } -- cgit v1.2.3 From 77bf6edb0138e3a38a2772248696f130dab45e34 Mon Sep 17 00:00:00 2001 From: "plundblad@google.com" Date: Wed, 21 Nov 2012 18:21:31 +0000 Subject: Add self braille client code. git-svn-id: https://eyes-free.googlecode.com/svn/trunk/braille/client/src/com/googlecode/eyesfree/braille@797 584082c0-ab3a-11dd-9ddb-6f86aeb5eef6 --- selfbraille/ISelfBrailleService.aidl | 28 ++++ selfbraille/SelfBrailleClient.java | 267 +++++++++++++++++++++++++++++++++++ selfbraille/WriteData.aidl | 19 +++ selfbraille/WriteData.java | 185 ++++++++++++++++++++++++ 4 files changed, 499 insertions(+) create mode 100644 selfbraille/ISelfBrailleService.aidl create mode 100644 selfbraille/SelfBrailleClient.java create mode 100644 selfbraille/WriteData.aidl create mode 100644 selfbraille/WriteData.java 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 -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 CREATOR = + new Parcelable.Creator() { + @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} + * Note: 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(); + } +} -- cgit v1.2.3