1 /* 2 * Copyright (C) 2012 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.eyesfree.braille.display; 18 19 import android.os.Message; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.RemoteException; 27 import android.util.Log; 28 29 /** 30 * A client for the braille display service. 31 */ 32 public class Display { 33 private static final String LOG_TAG = Display.class.getSimpleName(); 34 /** Service name used for connecting to the service. */ 35 public static final String ACTION_DISPLAY_SERVICE = 36 "com.googlecode.eyesfree.braille.service.ACTION_DISPLAY_SERVICE"; 37 38 /** Initial value, which is never reported to the listener. */ 39 private static final int STATE_UNKNOWN = -2; 40 public static final int STATE_ERROR = -1; 41 public static final int STATE_NOT_CONNECTED = 0; 42 public static final int STATE_CONNECTED = 1; 43 44 private final OnConnectionStateChangeListener 45 mConnectionStateChangeListener; 46 private final Context mContext; 47 private final DisplayHandler mHandler; 48 private volatile OnInputEventListener mInputEventListener; 49 private static final Intent mServiceIntent = 50 new Intent(ACTION_DISPLAY_SERVICE); 51 private Connection mConnection; 52 private int currentConnectionState = STATE_UNKNOWN; 53 private BrailleDisplayProperties mDisplayProperties; 54 private ServiceCallback mServiceCallback = new ServiceCallback(); 55 /** 56 * Delay before the first rebind attempt on bind error or service 57 * disconnect. 58 */ 59 private static final int REBIND_DELAY_MILLIS = 500; 60 private static final int MAX_REBIND_ATTEMPTS = 5; 61 private int mNumFailedBinds = 0; 62 63 /** 64 * A callback interface to get informed about connection state changes. 65 */ 66 public interface OnConnectionStateChangeListener { 67 void onConnectionStateChanged(int state); 68 } 69 70 /** 71 * A callback interface for input from the braille display. 72 */ 73 public interface OnInputEventListener { 74 void onInputEvent(BrailleInputEvent inputEvent); 75 } 76 77 /** 78 * Constructs an instance and connects to the braille display service. 79 * The current thread must have an {@link android.os.Looper} associated 80 * with it. Callbacks from this object will all be executed on the 81 * current thread. Connection state will be reported to {@code listener). 82 */ 83 public Display(Context context, OnConnectionStateChangeListener listener) { 84 this(context, listener, null); 85 } 86 87 /** 88 * Constructs an instance and connects to the braille display service. 89 * Callbacks from this object will all be executed on the thread 90 * associated with {@code handler}. If {@code handler} is {@code null}, 91 * the current thread must have an {@link android.os.Looper} associated 92 * with it, which will then be used to execute callbacks. Connection 93 * state will be reported to {@code listener). 94 */ 95 public Display(Context context, OnConnectionStateChangeListener listener, 96 Handler handler) { 97 mContext = context; 98 mConnectionStateChangeListener = listener; 99 if (handler == null) { 100 mHandler = new DisplayHandler(); 101 } else { 102 mHandler = new DisplayHandler(handler); 103 } 104 105 doBindService(); 106 } 107 108 /** 109 * Sets a {@code listener} for input events. {@code listener} can be 110 * {@code null} to remove a previously set listener. 111 */ 112 public void setOnInputEventListener(OnInputEventListener listener) { 113 mInputEventListener = listener; 114 } 115 116 /** 117 * Returns the display properties, or {@code null} if not connected 118 * to a display. 119 */ 120 public BrailleDisplayProperties getDisplayProperties() { 121 return mDisplayProperties; 122 } 123 124 /** 125 * Displays a given dots configuration on the braille display. 126 * @param patterns Dots configuration to be displayed. 127 */ 128 public void displayDots(byte[] patterns) { 129 IBrailleService localService = getBrailleService(); 130 if (localService != null) { 131 try { 132 localService.displayDots(patterns); 133 } catch (RemoteException ex) { 134 Log.e(LOG_TAG, "Error in displayDots", ex); 135 } 136 } else { 137 Log.v(LOG_TAG, "Error in displayDots: service not connected"); 138 } 139 } 140 141 /** 142 * Unbinds from the braille display service and deallocates any 143 * resources. This method should be called when the braille display 144 * is no longer in use by this client. 145 */ 146 public void shutdown() { 147 doUnbindService(); 148 } 149 150 // NOTE: The methods in this class will be executed in the main 151 // application thread. 152 private class Connection implements ServiceConnection { 153 private volatile IBrailleService mService; 154 155 @Override 156 public void onServiceConnected(ComponentName className, 157 IBinder binder) { 158 Log.i(LOG_TAG, "Connected to braille service"); 159 IBrailleService localService = 160 IBrailleService.Stub.asInterface(binder); 161 try { 162 localService.registerCallback(mServiceCallback); 163 mService = localService; 164 synchronized (mHandler) { 165 mNumFailedBinds = 0; 166 } 167 } catch (RemoteException e) { 168 // In this case the service has crashed before we could even do 169 // anything with it. 170 Log.e(LOG_TAG, "Failed to register callback on service", e); 171 // We should get a disconnected call and the rebind 172 // and failure reporting happens in that handler. 173 } 174 } 175 176 @Override 177 public void onServiceDisconnected(ComponentName className) { 178 mService = null; 179 Log.e(LOG_TAG, "Disconnected from braille service"); 180 // Report display disconnected for now, this will turn into a 181 // connected state or error state depending on how the retrying 182 // goes. 183 mHandler.reportConnectionState(STATE_NOT_CONNECTED, null); 184 mHandler.scheduleRebind(); 185 } 186 } 187 188 // NOTE: The methods of this class will be executed in the IPC 189 // thread pool and not on the main application thread. 190 private class ServiceCallback extends IBrailleServiceCallback.Stub { 191 @Override 192 public void onDisplayConnected( 193 BrailleDisplayProperties displayProperties) { 194 mHandler.reportConnectionState(STATE_CONNECTED, displayProperties); 195 } 196 197 @Override 198 public void onDisplayDisconnected() { 199 mHandler.reportConnectionState(STATE_NOT_CONNECTED, null); 200 } 201 202 @Override 203 public void onInput(BrailleInputEvent inputEvent) { 204 mHandler.reportInputEvent(inputEvent); 205 } 206 } 207 208 private void doBindService() { 209 Connection localConnection = new Connection(); 210 if (!mContext.bindService(mServiceIntent, localConnection, 211 Context.BIND_AUTO_CREATE)) { 212 Log.e(LOG_TAG, "Failed to bind Service"); 213 mHandler.scheduleRebind(); 214 return; 215 } 216 mConnection = localConnection; 217 Log.i(LOG_TAG, "Bound to braille service"); 218 } 219 220 private void doUnbindService() { 221 IBrailleService localService = getBrailleService(); 222 if (localService != null) { 223 try { 224 localService.unregisterCallback(mServiceCallback); 225 } catch (RemoteException e) { 226 // Nothing to do if the service can't be reached. 227 } 228 } 229 if (mConnection != null) { 230 mContext.unbindService(mConnection); 231 mConnection = null; 232 } 233 } 234 235 private IBrailleService getBrailleService() { 236 Connection localConnection = mConnection; 237 if (localConnection != null) { 238 return localConnection.mService; 239 } 240 return null; 241 } 242 243 private class DisplayHandler extends Handler { 244 private static final int MSG_REPORT_CONNECTION_STATE = 1; 245 private static final int MSG_REPORT_INPUT_EVENT = 2; 246 private static final int MSG_REBIND_SERVICE = 3; 247 248 public DisplayHandler() { 249 } 250 251 public DisplayHandler(Handler handler) { 252 super(handler.getLooper()); 253 } 254 255 public void reportConnectionState(final int newState, 256 final BrailleDisplayProperties displayProperties) { 257 obtainMessage(MSG_REPORT_CONNECTION_STATE, newState, 0, 258 displayProperties) 259 .sendToTarget(); 260 } 261 262 public void reportInputEvent(BrailleInputEvent event) { 263 obtainMessage(MSG_REPORT_INPUT_EVENT, event).sendToTarget(); 264 } 265 266 public void scheduleRebind() { 267 synchronized (this) { 268 if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) { 269 int delay = REBIND_DELAY_MILLIS << mNumFailedBinds; 270 sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay); 271 ++mNumFailedBinds; 272 Log.w(LOG_TAG, String.format( 273 "Will rebind to braille service in %d ms.", delay)); 274 } else { 275 reportConnectionState(STATE_ERROR, null); 276 } 277 } 278 } 279 280 @Override 281 public void handleMessage(Message msg) { 282 switch (msg.what) { 283 case MSG_REPORT_CONNECTION_STATE: 284 handleReportConnectionState(msg.arg1, 285 (BrailleDisplayProperties) msg.obj); 286 break; 287 case MSG_REPORT_INPUT_EVENT: 288 handleReportInputEvent((BrailleInputEvent) msg.obj); 289 break; 290 case MSG_REBIND_SERVICE: 291 handleRebindService(); 292 break; 293 } 294 } 295 296 private void handleReportConnectionState(int newState, 297 BrailleDisplayProperties displayProperties) { 298 mDisplayProperties = displayProperties; 299 if (newState != currentConnectionState 300 && mConnectionStateChangeListener != null) { 301 mConnectionStateChangeListener.onConnectionStateChanged( 302 newState); 303 } 304 currentConnectionState = newState; 305 } 306 307 private void handleReportInputEvent(BrailleInputEvent event) { 308 OnInputEventListener localListener = mInputEventListener; 309 if (localListener != null) { 310 localListener.onInputEvent(event); 311 } 312 } 313 314 private void handleRebindService() { 315 if (mConnection != null) { 316 doUnbindService(); 317 } 318 doBindService(); 319 } 320 } 321 } 322