Home | History | Annotate | Download | only in display
      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