Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (C) 2012 Google Inc. |
michael@0 | 3 | * |
michael@0 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
michael@0 | 5 | * use this file except in compliance with the License. You may obtain a copy of |
michael@0 | 6 | * the License at |
michael@0 | 7 | * |
michael@0 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 9 | * |
michael@0 | 10 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
michael@0 | 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
michael@0 | 13 | * License for the specific language governing permissions and limitations under |
michael@0 | 14 | * the License. |
michael@0 | 15 | */ |
michael@0 | 16 | |
michael@0 | 17 | package com.googlecode.eyesfree.braille.selfbraille; |
michael@0 | 18 | |
michael@0 | 19 | import android.content.ComponentName; |
michael@0 | 20 | import android.content.Context; |
michael@0 | 21 | import android.content.Intent; |
michael@0 | 22 | import android.content.ServiceConnection; |
michael@0 | 23 | import android.content.pm.PackageInfo; |
michael@0 | 24 | import android.content.pm.PackageManager; |
michael@0 | 25 | import android.content.pm.Signature; |
michael@0 | 26 | import android.os.Binder; |
michael@0 | 27 | import android.os.Handler; |
michael@0 | 28 | import android.os.IBinder; |
michael@0 | 29 | import android.os.Message; |
michael@0 | 30 | import android.os.RemoteException; |
michael@0 | 31 | import android.util.Log; |
michael@0 | 32 | |
michael@0 | 33 | import java.security.MessageDigest; |
michael@0 | 34 | import java.security.NoSuchAlgorithmException; |
michael@0 | 35 | |
michael@0 | 36 | /** |
michael@0 | 37 | * Client-side interface to the self brailling interface. |
michael@0 | 38 | * |
michael@0 | 39 | * Threading: Instances of this object should be created and shut down |
michael@0 | 40 | * in a thread with a {@link Looper} associated with it. Other methods may |
michael@0 | 41 | * be called on any thread. |
michael@0 | 42 | */ |
michael@0 | 43 | public class SelfBrailleClient { |
michael@0 | 44 | private static final String LOG_TAG = |
michael@0 | 45 | SelfBrailleClient.class.getSimpleName(); |
michael@0 | 46 | private static final String ACTION_SELF_BRAILLE_SERVICE = |
michael@0 | 47 | "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE"; |
michael@0 | 48 | private static final String BRAILLE_BACK_PACKAGE = |
michael@0 | 49 | "com.googlecode.eyesfree.brailleback"; |
michael@0 | 50 | private static final Intent mServiceIntent = |
michael@0 | 51 | new Intent(ACTION_SELF_BRAILLE_SERVICE) |
michael@0 | 52 | .setPackage(BRAILLE_BACK_PACKAGE); |
michael@0 | 53 | /** |
michael@0 | 54 | * SHA-1 hash value of the Eyes-Free release key certificate, used to sign |
michael@0 | 55 | * BrailleBack. It was generated from the keystore with: |
michael@0 | 56 | * $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \ |
michael@0 | 57 | * > cert |
michael@0 | 58 | * $ keytool -printcert -file cert |
michael@0 | 59 | */ |
michael@0 | 60 | // The typecasts are to silence a compiler warning about loss of precision |
michael@0 | 61 | private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] { |
michael@0 | 62 | (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D, |
michael@0 | 63 | (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4, |
michael@0 | 64 | (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B, |
michael@0 | 65 | (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76, |
michael@0 | 66 | (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61 |
michael@0 | 67 | }; |
michael@0 | 68 | /** |
michael@0 | 69 | * Delay before the first rebind attempt on bind error or service |
michael@0 | 70 | * disconnect. |
michael@0 | 71 | */ |
michael@0 | 72 | private static final int REBIND_DELAY_MILLIS = 500; |
michael@0 | 73 | private static final int MAX_REBIND_ATTEMPTS = 5; |
michael@0 | 74 | |
michael@0 | 75 | private final Binder mIdentity = new Binder(); |
michael@0 | 76 | private final Context mContext; |
michael@0 | 77 | private final boolean mAllowDebugService; |
michael@0 | 78 | private final SelfBrailleHandler mHandler = new SelfBrailleHandler(); |
michael@0 | 79 | private boolean mShutdown = false; |
michael@0 | 80 | |
michael@0 | 81 | /** |
michael@0 | 82 | * Written in handler thread, read in any thread calling methods on the |
michael@0 | 83 | * object. |
michael@0 | 84 | */ |
michael@0 | 85 | private volatile Connection mConnection; |
michael@0 | 86 | /** Protected by synchronizing on mHandler. */ |
michael@0 | 87 | private int mNumFailedBinds = 0; |
michael@0 | 88 | |
michael@0 | 89 | /** |
michael@0 | 90 | * Constructs an instance of this class. {@code context} is used to bind |
michael@0 | 91 | * to the self braille service. The current thread must have a Looper |
michael@0 | 92 | * associated with it. If {@code allowDebugService} is true, this instance |
michael@0 | 93 | * will connect to a BrailleBack service without requiring it to be signed |
michael@0 | 94 | * by the release key used to sign BrailleBack. |
michael@0 | 95 | */ |
michael@0 | 96 | public SelfBrailleClient(Context context, boolean allowDebugService) { |
michael@0 | 97 | mContext = context; |
michael@0 | 98 | mAllowDebugService = allowDebugService; |
michael@0 | 99 | doBindService(); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | /** |
michael@0 | 103 | * Shuts this instance down, deallocating any global resources it is using. |
michael@0 | 104 | * This method must be called on the same thread that created this object. |
michael@0 | 105 | */ |
michael@0 | 106 | public void shutdown() { |
michael@0 | 107 | mShutdown = true; |
michael@0 | 108 | doUnbindService(); |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | public void write(WriteData writeData) { |
michael@0 | 112 | writeData.validate(); |
michael@0 | 113 | ISelfBrailleService localService = getSelfBrailleService(); |
michael@0 | 114 | if (localService != null) { |
michael@0 | 115 | try { |
michael@0 | 116 | localService.write(mIdentity, writeData); |
michael@0 | 117 | } catch (RemoteException ex) { |
michael@0 | 118 | Log.e(LOG_TAG, "Self braille write failed", ex); |
michael@0 | 119 | } |
michael@0 | 120 | } |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | private void doBindService() { |
michael@0 | 124 | Connection localConnection = new Connection(); |
michael@0 | 125 | if (!mContext.bindService(mServiceIntent, localConnection, |
michael@0 | 126 | Context.BIND_AUTO_CREATE)) { |
michael@0 | 127 | Log.e(LOG_TAG, "Failed to bind to service"); |
michael@0 | 128 | mHandler.scheduleRebind(); |
michael@0 | 129 | return; |
michael@0 | 130 | } |
michael@0 | 131 | mConnection = localConnection; |
michael@0 | 132 | Log.i(LOG_TAG, "Bound to self braille service"); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | private void doUnbindService() { |
michael@0 | 136 | if (mConnection != null) { |
michael@0 | 137 | ISelfBrailleService localService = getSelfBrailleService(); |
michael@0 | 138 | if (localService != null) { |
michael@0 | 139 | try { |
michael@0 | 140 | localService.disconnect(mIdentity); |
michael@0 | 141 | } catch (RemoteException ex) { |
michael@0 | 142 | // Nothing to do. |
michael@0 | 143 | } |
michael@0 | 144 | } |
michael@0 | 145 | mContext.unbindService(mConnection); |
michael@0 | 146 | mConnection = null; |
michael@0 | 147 | } |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | private ISelfBrailleService getSelfBrailleService() { |
michael@0 | 151 | Connection localConnection = mConnection; |
michael@0 | 152 | if (localConnection != null) { |
michael@0 | 153 | return localConnection.mService; |
michael@0 | 154 | } |
michael@0 | 155 | return null; |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | private boolean verifyPackage() { |
michael@0 | 159 | PackageManager pm = mContext.getPackageManager(); |
michael@0 | 160 | PackageInfo pi; |
michael@0 | 161 | try { |
michael@0 | 162 | pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE, |
michael@0 | 163 | PackageManager.GET_SIGNATURES); |
michael@0 | 164 | } catch (PackageManager.NameNotFoundException ex) { |
michael@0 | 165 | Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE, |
michael@0 | 166 | ex); |
michael@0 | 167 | return false; |
michael@0 | 168 | } |
michael@0 | 169 | MessageDigest digest; |
michael@0 | 170 | try { |
michael@0 | 171 | digest = MessageDigest.getInstance("SHA-1"); |
michael@0 | 172 | } catch (NoSuchAlgorithmException ex) { |
michael@0 | 173 | Log.e(LOG_TAG, "SHA-1 not supported", ex); |
michael@0 | 174 | return false; |
michael@0 | 175 | } |
michael@0 | 176 | // Check if any of the certificates match our hash. |
michael@0 | 177 | for (Signature signature : pi.signatures) { |
michael@0 | 178 | digest.update(signature.toByteArray()); |
michael@0 | 179 | if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) { |
michael@0 | 180 | return true; |
michael@0 | 181 | } |
michael@0 | 182 | digest.reset(); |
michael@0 | 183 | } |
michael@0 | 184 | if (mAllowDebugService) { |
michael@0 | 185 | Log.w(LOG_TAG, String.format( |
michael@0 | 186 | "*** %s connected to BrailleBack with invalid (debug?) " |
michael@0 | 187 | + "signature ***", |
michael@0 | 188 | mContext.getPackageName())); |
michael@0 | 189 | return true; |
michael@0 | 190 | } |
michael@0 | 191 | return false; |
michael@0 | 192 | } |
michael@0 | 193 | private class Connection implements ServiceConnection { |
michael@0 | 194 | // Read in application threads, written in main thread. |
michael@0 | 195 | private volatile ISelfBrailleService mService; |
michael@0 | 196 | |
michael@0 | 197 | @Override |
michael@0 | 198 | public void onServiceConnected(ComponentName className, |
michael@0 | 199 | IBinder binder) { |
michael@0 | 200 | if (!verifyPackage()) { |
michael@0 | 201 | Log.w(LOG_TAG, String.format("Service certificate mismatch " |
michael@0 | 202 | + "for %s, dropping connection", |
michael@0 | 203 | BRAILLE_BACK_PACKAGE)); |
michael@0 | 204 | mHandler.unbindService(); |
michael@0 | 205 | return; |
michael@0 | 206 | } |
michael@0 | 207 | Log.i(LOG_TAG, "Connected to self braille service"); |
michael@0 | 208 | mService = ISelfBrailleService.Stub.asInterface(binder); |
michael@0 | 209 | synchronized (mHandler) { |
michael@0 | 210 | mNumFailedBinds = 0; |
michael@0 | 211 | } |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | @Override |
michael@0 | 215 | public void onServiceDisconnected(ComponentName className) { |
michael@0 | 216 | Log.e(LOG_TAG, "Disconnected from self braille service"); |
michael@0 | 217 | mService = null; |
michael@0 | 218 | // Retry by rebinding. |
michael@0 | 219 | mHandler.scheduleRebind(); |
michael@0 | 220 | } |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | private class SelfBrailleHandler extends Handler { |
michael@0 | 224 | private static final int MSG_REBIND_SERVICE = 1; |
michael@0 | 225 | private static final int MSG_UNBIND_SERVICE = 2; |
michael@0 | 226 | |
michael@0 | 227 | public void scheduleRebind() { |
michael@0 | 228 | synchronized (this) { |
michael@0 | 229 | if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) { |
michael@0 | 230 | int delay = REBIND_DELAY_MILLIS << mNumFailedBinds; |
michael@0 | 231 | sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay); |
michael@0 | 232 | ++mNumFailedBinds; |
michael@0 | 233 | } |
michael@0 | 234 | } |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | public void unbindService() { |
michael@0 | 238 | sendEmptyMessage(MSG_UNBIND_SERVICE); |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | @Override |
michael@0 | 242 | public void handleMessage(Message msg) { |
michael@0 | 243 | switch (msg.what) { |
michael@0 | 244 | case MSG_REBIND_SERVICE: |
michael@0 | 245 | handleRebindService(); |
michael@0 | 246 | break; |
michael@0 | 247 | case MSG_UNBIND_SERVICE: |
michael@0 | 248 | handleUnbindService(); |
michael@0 | 249 | break; |
michael@0 | 250 | } |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | private void handleRebindService() { |
michael@0 | 254 | if (mShutdown) { |
michael@0 | 255 | return; |
michael@0 | 256 | } |
michael@0 | 257 | if (mConnection != null) { |
michael@0 | 258 | doUnbindService(); |
michael@0 | 259 | } |
michael@0 | 260 | doBindService(); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | private void handleUnbindService() { |
michael@0 | 264 | doUnbindService(); |
michael@0 | 265 | } |
michael@0 | 266 | } |
michael@0 | 267 | } |