1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/thirdparty/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,267 @@ 1.4 +/* 1.5 + * Copyright (C) 2012 Google Inc. 1.6 + * 1.7 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not 1.8 + * use this file except in compliance with the License. You may obtain a copy of 1.9 + * the License at 1.10 + * 1.11 + * http://www.apache.org/licenses/LICENSE-2.0 1.12 + * 1.13 + * Unless required by applicable law or agreed to in writing, software 1.14 + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 1.15 + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 1.16 + * License for the specific language governing permissions and limitations under 1.17 + * the License. 1.18 + */ 1.19 + 1.20 +package com.googlecode.eyesfree.braille.selfbraille; 1.21 + 1.22 +import android.content.ComponentName; 1.23 +import android.content.Context; 1.24 +import android.content.Intent; 1.25 +import android.content.ServiceConnection; 1.26 +import android.content.pm.PackageInfo; 1.27 +import android.content.pm.PackageManager; 1.28 +import android.content.pm.Signature; 1.29 +import android.os.Binder; 1.30 +import android.os.Handler; 1.31 +import android.os.IBinder; 1.32 +import android.os.Message; 1.33 +import android.os.RemoteException; 1.34 +import android.util.Log; 1.35 + 1.36 +import java.security.MessageDigest; 1.37 +import java.security.NoSuchAlgorithmException; 1.38 + 1.39 +/** 1.40 + * Client-side interface to the self brailling interface. 1.41 + * 1.42 + * Threading: Instances of this object should be created and shut down 1.43 + * in a thread with a {@link Looper} associated with it. Other methods may 1.44 + * be called on any thread. 1.45 + */ 1.46 +public class SelfBrailleClient { 1.47 + private static final String LOG_TAG = 1.48 + SelfBrailleClient.class.getSimpleName(); 1.49 + private static final String ACTION_SELF_BRAILLE_SERVICE = 1.50 + "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE"; 1.51 + private static final String BRAILLE_BACK_PACKAGE = 1.52 + "com.googlecode.eyesfree.brailleback"; 1.53 + private static final Intent mServiceIntent = 1.54 + new Intent(ACTION_SELF_BRAILLE_SERVICE) 1.55 + .setPackage(BRAILLE_BACK_PACKAGE); 1.56 + /** 1.57 + * SHA-1 hash value of the Eyes-Free release key certificate, used to sign 1.58 + * BrailleBack. It was generated from the keystore with: 1.59 + * $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \ 1.60 + * > cert 1.61 + * $ keytool -printcert -file cert 1.62 + */ 1.63 + // The typecasts are to silence a compiler warning about loss of precision 1.64 + private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] { 1.65 + (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D, 1.66 + (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4, 1.67 + (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B, 1.68 + (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76, 1.69 + (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61 1.70 + }; 1.71 + /** 1.72 + * Delay before the first rebind attempt on bind error or service 1.73 + * disconnect. 1.74 + */ 1.75 + private static final int REBIND_DELAY_MILLIS = 500; 1.76 + private static final int MAX_REBIND_ATTEMPTS = 5; 1.77 + 1.78 + private final Binder mIdentity = new Binder(); 1.79 + private final Context mContext; 1.80 + private final boolean mAllowDebugService; 1.81 + private final SelfBrailleHandler mHandler = new SelfBrailleHandler(); 1.82 + private boolean mShutdown = false; 1.83 + 1.84 + /** 1.85 + * Written in handler thread, read in any thread calling methods on the 1.86 + * object. 1.87 + */ 1.88 + private volatile Connection mConnection; 1.89 + /** Protected by synchronizing on mHandler. */ 1.90 + private int mNumFailedBinds = 0; 1.91 + 1.92 + /** 1.93 + * Constructs an instance of this class. {@code context} is used to bind 1.94 + * to the self braille service. The current thread must have a Looper 1.95 + * associated with it. If {@code allowDebugService} is true, this instance 1.96 + * will connect to a BrailleBack service without requiring it to be signed 1.97 + * by the release key used to sign BrailleBack. 1.98 + */ 1.99 + public SelfBrailleClient(Context context, boolean allowDebugService) { 1.100 + mContext = context; 1.101 + mAllowDebugService = allowDebugService; 1.102 + doBindService(); 1.103 + } 1.104 + 1.105 + /** 1.106 + * Shuts this instance down, deallocating any global resources it is using. 1.107 + * This method must be called on the same thread that created this object. 1.108 + */ 1.109 + public void shutdown() { 1.110 + mShutdown = true; 1.111 + doUnbindService(); 1.112 + } 1.113 + 1.114 + public void write(WriteData writeData) { 1.115 + writeData.validate(); 1.116 + ISelfBrailleService localService = getSelfBrailleService(); 1.117 + if (localService != null) { 1.118 + try { 1.119 + localService.write(mIdentity, writeData); 1.120 + } catch (RemoteException ex) { 1.121 + Log.e(LOG_TAG, "Self braille write failed", ex); 1.122 + } 1.123 + } 1.124 + } 1.125 + 1.126 + private void doBindService() { 1.127 + Connection localConnection = new Connection(); 1.128 + if (!mContext.bindService(mServiceIntent, localConnection, 1.129 + Context.BIND_AUTO_CREATE)) { 1.130 + Log.e(LOG_TAG, "Failed to bind to service"); 1.131 + mHandler.scheduleRebind(); 1.132 + return; 1.133 + } 1.134 + mConnection = localConnection; 1.135 + Log.i(LOG_TAG, "Bound to self braille service"); 1.136 + } 1.137 + 1.138 + private void doUnbindService() { 1.139 + if (mConnection != null) { 1.140 + ISelfBrailleService localService = getSelfBrailleService(); 1.141 + if (localService != null) { 1.142 + try { 1.143 + localService.disconnect(mIdentity); 1.144 + } catch (RemoteException ex) { 1.145 + // Nothing to do. 1.146 + } 1.147 + } 1.148 + mContext.unbindService(mConnection); 1.149 + mConnection = null; 1.150 + } 1.151 + } 1.152 + 1.153 + private ISelfBrailleService getSelfBrailleService() { 1.154 + Connection localConnection = mConnection; 1.155 + if (localConnection != null) { 1.156 + return localConnection.mService; 1.157 + } 1.158 + return null; 1.159 + } 1.160 + 1.161 + private boolean verifyPackage() { 1.162 + PackageManager pm = mContext.getPackageManager(); 1.163 + PackageInfo pi; 1.164 + try { 1.165 + pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE, 1.166 + PackageManager.GET_SIGNATURES); 1.167 + } catch (PackageManager.NameNotFoundException ex) { 1.168 + Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE, 1.169 + ex); 1.170 + return false; 1.171 + } 1.172 + MessageDigest digest; 1.173 + try { 1.174 + digest = MessageDigest.getInstance("SHA-1"); 1.175 + } catch (NoSuchAlgorithmException ex) { 1.176 + Log.e(LOG_TAG, "SHA-1 not supported", ex); 1.177 + return false; 1.178 + } 1.179 + // Check if any of the certificates match our hash. 1.180 + for (Signature signature : pi.signatures) { 1.181 + digest.update(signature.toByteArray()); 1.182 + if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) { 1.183 + return true; 1.184 + } 1.185 + digest.reset(); 1.186 + } 1.187 + if (mAllowDebugService) { 1.188 + Log.w(LOG_TAG, String.format( 1.189 + "*** %s connected to BrailleBack with invalid (debug?) " 1.190 + + "signature ***", 1.191 + mContext.getPackageName())); 1.192 + return true; 1.193 + } 1.194 + return false; 1.195 + } 1.196 + private class Connection implements ServiceConnection { 1.197 + // Read in application threads, written in main thread. 1.198 + private volatile ISelfBrailleService mService; 1.199 + 1.200 + @Override 1.201 + public void onServiceConnected(ComponentName className, 1.202 + IBinder binder) { 1.203 + if (!verifyPackage()) { 1.204 + Log.w(LOG_TAG, String.format("Service certificate mismatch " 1.205 + + "for %s, dropping connection", 1.206 + BRAILLE_BACK_PACKAGE)); 1.207 + mHandler.unbindService(); 1.208 + return; 1.209 + } 1.210 + Log.i(LOG_TAG, "Connected to self braille service"); 1.211 + mService = ISelfBrailleService.Stub.asInterface(binder); 1.212 + synchronized (mHandler) { 1.213 + mNumFailedBinds = 0; 1.214 + } 1.215 + } 1.216 + 1.217 + @Override 1.218 + public void onServiceDisconnected(ComponentName className) { 1.219 + Log.e(LOG_TAG, "Disconnected from self braille service"); 1.220 + mService = null; 1.221 + // Retry by rebinding. 1.222 + mHandler.scheduleRebind(); 1.223 + } 1.224 + } 1.225 + 1.226 + private class SelfBrailleHandler extends Handler { 1.227 + private static final int MSG_REBIND_SERVICE = 1; 1.228 + private static final int MSG_UNBIND_SERVICE = 2; 1.229 + 1.230 + public void scheduleRebind() { 1.231 + synchronized (this) { 1.232 + if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) { 1.233 + int delay = REBIND_DELAY_MILLIS << mNumFailedBinds; 1.234 + sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay); 1.235 + ++mNumFailedBinds; 1.236 + } 1.237 + } 1.238 + } 1.239 + 1.240 + public void unbindService() { 1.241 + sendEmptyMessage(MSG_UNBIND_SERVICE); 1.242 + } 1.243 + 1.244 + @Override 1.245 + public void handleMessage(Message msg) { 1.246 + switch (msg.what) { 1.247 + case MSG_REBIND_SERVICE: 1.248 + handleRebindService(); 1.249 + break; 1.250 + case MSG_UNBIND_SERVICE: 1.251 + handleUnbindService(); 1.252 + break; 1.253 + } 1.254 + } 1.255 + 1.256 + private void handleRebindService() { 1.257 + if (mShutdown) { 1.258 + return; 1.259 + } 1.260 + if (mConnection != null) { 1.261 + doUnbindService(); 1.262 + } 1.263 + doBindService(); 1.264 + } 1.265 + 1.266 + private void handleUnbindService() { 1.267 + doUnbindService(); 1.268 + } 1.269 + } 1.270 +}