summaryrefslogtreecommitdiff
path: root/display/Display.java
diff options
context:
space:
mode:
Diffstat (limited to 'display/Display.java')
-rw-r--r--display/Display.java321
1 files changed, 321 insertions, 0 deletions
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();
+ }
+ }
+}