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