/* * 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(); } } }