HostEmulationManager.java revision 75f63db568f953e935e62cb3046d167b881979c8
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.nfc.cardemulation; 18 19import android.app.ActivityManager; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.ServiceConnection; 24import android.nfc.cardemulation.ApduServiceInfo; 25import android.nfc.cardemulation.HostApduService; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.IBinder; 29import android.os.Message; 30import android.os.Messenger; 31import android.os.RemoteException; 32import android.os.UserHandle; 33import android.util.Log; 34import com.android.nfc.NfcService; 35 36import java.util.ArrayList; 37 38public class HostEmulationManager { 39 static final String TAG = "HostEmulationManager"; 40 41 static final int STATE_IDLE = 0; 42 static final int STATE_W4_SELECT = 1; 43 static final int STATE_W4_SERVICE = 2; 44 static final int STATE_XFER = 3; 45 46 final Context mContext; 47 final RegisteredServicesCache mAidCache; 48 final Messenger mMessenger = new Messenger (new MessageHandler()); 49 50 final Object mLock; 51 52 // Variables below protected by mLock 53 boolean mBound; 54 boolean mClearNextTapDefault; 55 Messenger mService; 56 int mState; 57 byte[] mSelectApdu; 58 59 public HostEmulationManager(Context context, RegisteredServicesCache aidCache) { 60 mContext = context; 61 mLock = new Object(); 62 mAidCache = aidCache; 63 mState = STATE_IDLE; 64 } 65 66 public void notifyHostEmulationActivated() { 67 Log.d(TAG, "notifyHostEmulationActivated"); 68 synchronized (mLock) { 69 mClearNextTapDefault = mAidCache.isNextTapOverriden(); 70 if (mState != STATE_IDLE) { 71 Log.e(TAG, "Got activation event in non-idle state"); 72 if (mState >= STATE_W4_SERVICE) { 73 unbindServiceLocked(); 74 } 75 } 76 mState = STATE_W4_SELECT; 77 } 78 } 79 80 public void notifyHostEmulationData(byte[] data) { 81 Log.d(TAG, "notifyHostEmulationData"); 82 String selectAid = findSelectAid(data); 83 synchronized (mLock) { 84 switch (mState) { 85 case STATE_IDLE: 86 Log.e(TAG, "Got data in idle state"); 87 break; 88 case STATE_W4_SELECT: 89 if (selectAid != null) { 90 mSelectApdu = data; 91 if (dispatchAidLocked(selectAid)) 92 mState = STATE_W4_SERVICE; 93 } else { 94 Log.d(TAG, "Dropping non-select APDU in STATE_W4_SELECT"); 95 } 96 break; 97 case STATE_W4_SERVICE: 98 if (selectAid != null) { 99 // This should normally not happen, but deal with it gracefully 100 unbindServiceLocked(); 101 mSelectApdu = data; 102 if (dispatchAidLocked(selectAid)) 103 mState = STATE_W4_SERVICE; 104 } else { 105 Log.d(TAG, "Unexpected APDU in STATE_W4_SERVICE"); 106 } 107 break; 108 case STATE_XFER: 109 // TODO if we get another select we may need to resolve 110 // it to a different service 111 if (mService != null) { 112 Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU); 113 Bundle dataBundle = new Bundle(); 114 dataBundle.putByteArray("data", data); 115 msg.setData(dataBundle); 116 msg.replyTo = mMessenger; 117 try { 118 Log.d(TAG, "Sending data to service"); 119 mService.send(msg); 120 } catch (RemoteException e) { 121 Log.e(TAG, "Remote service has died, dropping APDU"); 122 } 123 } else { 124 Log.d(TAG, "Service no longer bound, dropping APDU"); 125 } 126 break; 127 } 128 } 129 } 130 131 public void notifyNostEmulationDeactivated() { 132 Log.d(TAG, "notifyHostEmulationDeactivated"); 133 synchronized (mLock) { 134 if (mClearNextTapDefault) { 135 mAidCache.setDefaultForNextTap(ActivityManager.getCurrentUser(), null); 136 } 137 if (mState == STATE_IDLE) { 138 Log.e(TAG, "Got deactivation event while in idle state"); 139 return; 140 } 141 if (mService != null) { 142 // Send a MSG_DEACTIVATED 143 Message msg = Message.obtain(null, HostApduService.MSG_DEACTIVATED); 144 try { 145 mService.send(msg); 146 } catch (RemoteException e) { 147 // Don't care 148 } 149 } 150 unbindServiceLocked(); 151 mState = STATE_IDLE; 152 } 153 } 154 155 void unbindServiceLocked() { 156 mContext.unbindService(mConnection); 157 synchronized (mLock) { 158 mBound = false; 159 mService = null; 160 } 161 } 162 163 void launchResolver(ArrayList<ComponentName> components, String category) { 164 Intent intent = new Intent(mContext, AppChooserActivity.class); 165 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 166 intent.putParcelableArrayListExtra(AppChooserActivity.EXTRA_COMPONENTS, components); 167 intent.putExtra(AppChooserActivity.EXTRA_CATEGORY, category); 168 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 169 } 170 171 String findSelectAid(byte[] data) { 172 if (data == null || data.length < 6) { 173 Log.d(TAG, "Data size too small for SELECT APDU"); 174 } 175 // TODO we'll support only logical channel 0 in CLA? 176 // TODO support chaining bits in CLA? 177 // TODO what about selection using DF/EF identifiers and/or path? 178 // TODO what about P2? 179 // TODO what about the default selection status? 180 if (data[0] == 0x00 && data[1] == (byte)0xA4 && data[2] == 0x04) { 181 int aidLength = data[4]; 182 if (data.length < 5 + aidLength) { 183 return null; 184 } 185 return bytesToString(data, 5, aidLength); 186 } 187 return null; 188 } 189 190 boolean dispatchAidLocked(String aid) { 191 Log.d(TAG, "dispatchAidLocked"); 192 // Regardless of what happens, if we're having a tap again 193 // activity up, close it 194 Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); 195 intent.setPackage("com.android.nfc"); 196 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 197 int userId = ActivityManager.getCurrentUser(); 198 ArrayList<ApduServiceInfo> matchingServices = mAidCache.resolveAidPrefix(userId, aid); 199 if (matchingServices == null || matchingServices.size() == 0) { 200 // TODO return 6A82? 201 Log.e(TAG, "Could not find matching services for AID " + aid); 202 } else if (matchingServices.size() == 1) { 203 ComponentName service = matchingServices.get(0).getComponent(); 204 Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE); 205 aidIntent.setComponent(service); 206 Log.e(TAG, "Resolved to service " + service); 207 if (mContext.bindServiceAsUser(aidIntent, mConnection, Context.BIND_AUTO_CREATE, 208 new UserHandle(userId))) { 209 return true; 210 } else { 211 Log.e(TAG, "Could not bind service"); 212 } 213 } else { 214 final ArrayList<ComponentName> components = new ArrayList<ComponentName>(); 215 for (ApduServiceInfo service : matchingServices) { 216 components.add(service.getComponent()); 217 } 218 // Get corresponding category 219 String category = mAidCache.getCategoryForAid(userId, aid); 220 launchResolver(components, category); 221 } 222 return false; 223 } 224 225 private ServiceConnection mConnection = new ServiceConnection() { 226 @Override 227 public void onServiceConnected(ComponentName name, IBinder service) { 228 synchronized (mLock) { 229 mService = new Messenger(service); 230 mBound = true; 231 Log.d(TAG, "Service bound"); 232 mState = STATE_XFER; 233 // Send pending select APDU 234 if (mSelectApdu != null) { 235 Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU); 236 Bundle dataBundle = new Bundle(); 237 dataBundle.putByteArray("data", mSelectApdu); 238 msg.setData(dataBundle); 239 msg.replyTo = mMessenger; 240 try { 241 mService.send(msg); 242 } catch (RemoteException e) { 243 Log.d(TAG, "Service gone!"); 244 } 245 mSelectApdu = null; 246 } 247 } 248 } 249 250 @Override 251 public void onServiceDisconnected(ComponentName name) { 252 synchronized (mLock) { 253 Log.d(TAG, "Service unbound"); 254 mService = null; 255 mBound = false; 256 } 257 } 258 }; 259 260 class MessageHandler extends Handler { 261 @Override 262 public void handleMessage(Message msg) { 263 if (msg.what == HostApduService.MSG_RESPONSE_APDU) { 264 Bundle dataBundle = msg.getData(); 265 if (dataBundle == null) { 266 return; 267 } 268 byte[] data = dataBundle.getByteArray("data"); 269 if (data == null || data.length == 0) { 270 Log.e(TAG, "Dropping empty R-APDU"); 271 return; 272 } 273 int state; 274 synchronized(mLock) { 275 state = mState; 276 } 277 if (state == STATE_XFER) { 278 Log.d(TAG, "Sending data"); 279 NfcService.getInstance().sendData(data); 280 } else { 281 Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state)); 282 } 283 } 284 } 285 } 286 287 static String bytesToString(byte[] bytes, int offset, int length) { 288 final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; 289 char[] chars = new char[length * 2]; 290 int byteValue; 291 for (int j = 0; j < length; j++) { 292 byteValue = bytes[offset + j] & 0xFF; 293 chars[j * 2] = hexChars[byteValue >>> 4]; 294 chars[j * 2 + 1] = hexChars[byteValue & 0x0F]; 295 } 296 return new String(chars); 297 } 298} 299