| /* |
| * Copyright (C) 2012 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package com.googlecode.eyesfree.braille.selfbraille; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.Signature; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| |
| /** |
| * Client-side interface to the self brailling interface. |
| * |
| * Threading: Instances of this object should be created and shut down |
| * in a thread with a {@link Looper} associated with it. Other methods may |
| * be called on any thread. |
| */ |
| public class SelfBrailleClient { |
| private static final String LOG_TAG = |
| SelfBrailleClient.class.getSimpleName(); |
| private static final String ACTION_SELF_BRAILLE_SERVICE = |
| "com.googlecode.eyesfree.braille.service.ACTION_SELF_BRAILLE_SERVICE"; |
| private static final String BRAILLE_BACK_PACKAGE = |
| "com.googlecode.eyesfree.brailleback"; |
| private static final Intent mServiceIntent = |
| new Intent(ACTION_SELF_BRAILLE_SERVICE) |
| .setPackage(BRAILLE_BACK_PACKAGE); |
| /** |
| * SHA-1 hash value of the Eyes-Free release key certificate, used to sign |
| * BrailleBack. It was generated from the keystore with: |
| * $ keytool -exportcert -keystore <keystorefile> -alias android.keystore \ |
| * > cert |
| * $ keytool -printcert -file cert |
| */ |
| // The typecasts are to silence a compiler warning about loss of precision |
| private static final byte[] EYES_FREE_CERT_SHA1 = new byte[] { |
| (byte) 0x9B, (byte) 0x42, (byte) 0x4C, (byte) 0x2D, |
| (byte) 0x27, (byte) 0xAD, (byte) 0x51, (byte) 0xA4, |
| (byte) 0x2A, (byte) 0x33, (byte) 0x7E, (byte) 0x0B, |
| (byte) 0xB6, (byte) 0x99, (byte) 0x1C, (byte) 0x76, |
| (byte) 0xEC, (byte) 0xA4, (byte) 0x44, (byte) 0x61 |
| }; |
| /** |
| * Delay before the first rebind attempt on bind error or service |
| * disconnect. |
| */ |
| private static final int REBIND_DELAY_MILLIS = 500; |
| private static final int MAX_REBIND_ATTEMPTS = 5; |
| |
| private final Binder mIdentity = new Binder(); |
| private final Context mContext; |
| private final boolean mAllowDebugService; |
| private final SelfBrailleHandler mHandler = new SelfBrailleHandler(); |
| private boolean mShutdown = false; |
| |
| /** |
| * Written in handler thread, read in any thread calling methods on the |
| * object. |
| */ |
| private volatile Connection mConnection; |
| /** Protected by synchronizing on mHandler. */ |
| private int mNumFailedBinds = 0; |
| |
| /** |
| * Constructs an instance of this class. {@code context} is used to bind |
| * to the self braille service. The current thread must have a Looper |
| * associated with it. If {@code allowDebugService} is true, this instance |
| * will connect to a BrailleBack service without requiring it to be signed |
| * by the release key used to sign BrailleBack. |
| */ |
| public SelfBrailleClient(Context context, boolean allowDebugService) { |
| mContext = context; |
| mAllowDebugService = allowDebugService; |
| doBindService(); |
| } |
| |
| /** |
| * Shuts this instance down, deallocating any global resources it is using. |
| * This method must be called on the same thread that created this object. |
| */ |
| public void shutdown() { |
| mShutdown = true; |
| doUnbindService(); |
| } |
| |
| public void write(WriteData writeData) { |
| writeData.validate(); |
| ISelfBrailleService localService = getSelfBrailleService(); |
| if (localService != null) { |
| try { |
| localService.write(mIdentity, writeData); |
| } catch (RemoteException ex) { |
| Log.e(LOG_TAG, "Self braille write failed", ex); |
| } |
| } |
| } |
| |
| private void doBindService() { |
| Connection localConnection = new Connection(); |
| if (!mContext.bindService(mServiceIntent, localConnection, |
| Context.BIND_AUTO_CREATE)) { |
| Log.e(LOG_TAG, "Failed to bind to service"); |
| mHandler.scheduleRebind(); |
| return; |
| } |
| mConnection = localConnection; |
| Log.i(LOG_TAG, "Bound to self braille service"); |
| } |
| |
| private void doUnbindService() { |
| if (mConnection != null) { |
| ISelfBrailleService localService = getSelfBrailleService(); |
| if (localService != null) { |
| try { |
| localService.disconnect(mIdentity); |
| } catch (RemoteException ex) { |
| // Nothing to do. |
| } |
| } |
| mContext.unbindService(mConnection); |
| mConnection = null; |
| } |
| } |
| |
| private ISelfBrailleService getSelfBrailleService() { |
| Connection localConnection = mConnection; |
| if (localConnection != null) { |
| return localConnection.mService; |
| } |
| return null; |
| } |
| |
| private boolean verifyPackage() { |
| PackageManager pm = mContext.getPackageManager(); |
| PackageInfo pi; |
| try { |
| pi = pm.getPackageInfo(BRAILLE_BACK_PACKAGE, |
| PackageManager.GET_SIGNATURES); |
| } catch (PackageManager.NameNotFoundException ex) { |
| Log.w(LOG_TAG, "Can't verify package " + BRAILLE_BACK_PACKAGE, |
| ex); |
| return false; |
| } |
| MessageDigest digest; |
| try { |
| digest = MessageDigest.getInstance("SHA-1"); |
| } catch (NoSuchAlgorithmException ex) { |
| Log.e(LOG_TAG, "SHA-1 not supported", ex); |
| return false; |
| } |
| // Check if any of the certificates match our hash. |
| for (Signature signature : pi.signatures) { |
| digest.update(signature.toByteArray()); |
| if (MessageDigest.isEqual(EYES_FREE_CERT_SHA1, digest.digest())) { |
| return true; |
| } |
| digest.reset(); |
| } |
| if (mAllowDebugService) { |
| Log.w(LOG_TAG, String.format( |
| "*** %s connected to BrailleBack with invalid (debug?) " |
| + "signature ***", |
| mContext.getPackageName())); |
| return true; |
| } |
| return false; |
| } |
| private class Connection implements ServiceConnection { |
| // Read in application threads, written in main thread. |
| private volatile ISelfBrailleService mService; |
| |
| @Override |
| public void onServiceConnected(ComponentName className, |
| IBinder binder) { |
| if (!verifyPackage()) { |
| Log.w(LOG_TAG, String.format("Service certificate mismatch " |
| + "for %s, dropping connection", |
| BRAILLE_BACK_PACKAGE)); |
| mHandler.unbindService(); |
| return; |
| } |
| Log.i(LOG_TAG, "Connected to self braille service"); |
| mService = ISelfBrailleService.Stub.asInterface(binder); |
| synchronized (mHandler) { |
| mNumFailedBinds = 0; |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName className) { |
| Log.e(LOG_TAG, "Disconnected from self braille service"); |
| mService = null; |
| // Retry by rebinding. |
| mHandler.scheduleRebind(); |
| } |
| } |
| |
| private class SelfBrailleHandler extends Handler { |
| private static final int MSG_REBIND_SERVICE = 1; |
| private static final int MSG_UNBIND_SERVICE = 2; |
| |
| public void scheduleRebind() { |
| synchronized (this) { |
| if (mNumFailedBinds < MAX_REBIND_ATTEMPTS) { |
| int delay = REBIND_DELAY_MILLIS << mNumFailedBinds; |
| sendEmptyMessageDelayed(MSG_REBIND_SERVICE, delay); |
| ++mNumFailedBinds; |
| } |
| } |
| } |
| |
| public void unbindService() { |
| sendEmptyMessage(MSG_UNBIND_SERVICE); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_REBIND_SERVICE: |
| handleRebindService(); |
| break; |
| case MSG_UNBIND_SERVICE: |
| handleUnbindService(); |
| break; |
| } |
| } |
| |
| private void handleRebindService() { |
| if (mShutdown) { |
| return; |
| } |
| if (mConnection != null) { |
| doUnbindService(); |
| } |
| doBindService(); |
| } |
| |
| private void handleUnbindService() { |
| doUnbindService(); |
| } |
| } |
| } |