1 /* 2 * Copyright (C) 2015 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 17 package com.android.nfc.cardemulation; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.nfc.cardemulation.NfcFServiceInfo; 24 import android.nfc.cardemulation.HostNfcFService; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Message; 29 import android.os.Messenger; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.util.Log; 33 34 import com.android.nfc.NfcService; 35 36 import java.io.FileDescriptor; 37 import java.io.PrintWriter; 38 39 import android.util.StatsLog; 40 41 public class HostNfcFEmulationManager { 42 static final String TAG = "HostNfcFEmulationManager"; 43 static final boolean DBG = false; 44 45 static final int STATE_IDLE = 0; 46 static final int STATE_W4_SERVICE = 1; 47 static final int STATE_XFER = 2; 48 49 /** NFCID2 length */ 50 static final int NFCID2_LENGTH = 8; 51 52 /** Minimum NFC-F packets including length, command code and NFCID2 */ 53 static final int MINIMUM_NFCF_PACKET_LENGTH = 10; 54 55 final Context mContext; 56 final RegisteredT3tIdentifiersCache mT3tIdentifiersCache; 57 final Messenger mMessenger = new Messenger (new MessageHandler()); 58 final Object mLock; 59 60 // All variables below protected by mLock 61 ComponentName mEnabledFgServiceName; 62 63 Messenger mService; 64 boolean mServiceBound; 65 ComponentName mServiceName; 66 67 // mActiveService denotes the service interface 68 // that is the current active one, until a new packet 69 // comes in that may be resolved to a different service. 70 // On deactivation, mActiveService stops being valid. 71 Messenger mActiveService; 72 ComponentName mActiveServiceName; 73 74 int mState; 75 byte[] mPendingPacket; 76 HostNfcFEmulationManager(Context context, RegisteredT3tIdentifiersCache t3tIdentifiersCache)77 public HostNfcFEmulationManager(Context context, 78 RegisteredT3tIdentifiersCache t3tIdentifiersCache) { 79 mContext = context; 80 mLock = new Object(); 81 mEnabledFgServiceName = null; 82 mT3tIdentifiersCache = t3tIdentifiersCache; 83 mState = STATE_IDLE; 84 } 85 onEnabledForegroundNfcFServiceChanged(ComponentName service)86 public void onEnabledForegroundNfcFServiceChanged(ComponentName service) { 87 synchronized (mLock) { 88 mEnabledFgServiceName = service; 89 if (service == null) { 90 sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 91 unbindServiceIfNeededLocked(); 92 } 93 } 94 } 95 onHostEmulationActivated()96 public void onHostEmulationActivated() { 97 if (DBG) Log.d(TAG, "notifyHostEmulationActivated"); 98 } 99 onHostEmulationData(byte[] data)100 public void onHostEmulationData(byte[] data) { 101 if (DBG) Log.d(TAG, "notifyHostEmulationData"); 102 String nfcid2 = findNfcid2(data); 103 ComponentName resolvedServiceName = null; 104 synchronized (mLock) { 105 if (nfcid2 != null) { 106 NfcFServiceInfo resolvedService = mT3tIdentifiersCache.resolveNfcid2(nfcid2); 107 if (resolvedService != null) { 108 resolvedServiceName = resolvedService.getComponent(); 109 } 110 } 111 if (resolvedServiceName == null) { 112 if (mActiveServiceName == null) { 113 return; 114 } 115 resolvedServiceName = mActiveServiceName; 116 } 117 // Check if resolvedService is actually currently enabled 118 if (mEnabledFgServiceName == null || 119 !mEnabledFgServiceName.equals(resolvedServiceName)) { 120 return; 121 } 122 if (DBG) Log.d(TAG, "resolvedServiceName: " + resolvedServiceName.toString() + 123 "mState: " + String.valueOf(mState)); 124 switch (mState) { 125 case STATE_IDLE: 126 Messenger existingService = bindServiceIfNeededLocked(resolvedServiceName); 127 if (existingService != null) { 128 Log.d(TAG, "Binding to existing service"); 129 mState = STATE_XFER; 130 sendDataToServiceLocked(existingService, data); 131 } else { 132 // Waiting for service to be bound 133 Log.d(TAG, "Waiting for new service."); 134 // Queue packet to be used 135 mPendingPacket = data; 136 mState = STATE_W4_SERVICE; 137 } 138 StatsLog.write(StatsLog.NFC_CARDEMULATION_OCCURRED, 139 StatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_PAYMENT, 140 "HCEF"); 141 break; 142 case STATE_W4_SERVICE: 143 Log.d(TAG, "Unexpected packet in STATE_W4_SERVICE"); 144 break; 145 case STATE_XFER: 146 // Regular packet data 147 sendDataToServiceLocked(mActiveService, data); 148 break; 149 } 150 } 151 } 152 onHostEmulationDeactivated()153 public void onHostEmulationDeactivated() { 154 if (DBG) Log.d(TAG, "notifyHostEmulationDeactivated"); 155 synchronized (mLock) { 156 sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 157 mActiveService = null; 158 mActiveServiceName = null; 159 unbindServiceIfNeededLocked(); 160 mState = STATE_IDLE; 161 } 162 } 163 onNfcDisabled()164 public void onNfcDisabled() { 165 synchronized (mLock) { 166 sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 167 mEnabledFgServiceName = null; 168 mActiveService = null; 169 mActiveServiceName = null; 170 unbindServiceIfNeededLocked(); 171 mState = STATE_IDLE; 172 } 173 } 174 onUserSwitched()175 public void onUserSwitched() { 176 synchronized (mLock) { 177 sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 178 mEnabledFgServiceName = null; 179 mActiveService = null; 180 mActiveServiceName = null; 181 unbindServiceIfNeededLocked(); 182 mState = STATE_IDLE; 183 } 184 } 185 sendDataToServiceLocked(Messenger service, byte[] data)186 void sendDataToServiceLocked(Messenger service, byte[] data) { 187 if (DBG) Log.d(TAG, "sendDataToServiceLocked"); 188 if (DBG) { 189 Log.d(TAG, "service: " + 190 (service != null ? service.toString() : "null")); 191 Log.d(TAG, "mActiveService: " + 192 (mActiveService != null ? mActiveService.toString() : "null")); 193 } 194 if (service != mActiveService) { 195 sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS); 196 mActiveService = service; 197 mActiveServiceName = mServiceName; 198 } 199 Message msg = Message.obtain(null, HostNfcFService.MSG_COMMAND_PACKET); 200 Bundle dataBundle = new Bundle(); 201 dataBundle.putByteArray("data", data); 202 msg.setData(dataBundle); 203 msg.replyTo = mMessenger; 204 try { 205 Log.d(TAG, "Sending data to service"); 206 if (DBG) Log.d(TAG, "data: " + getByteDump(data)); 207 mActiveService.send(msg); 208 } catch (RemoteException e) { 209 Log.e(TAG, "Remote service has died, dropping packet"); 210 } 211 } 212 sendDeactivateToActiveServiceLocked(int reason)213 void sendDeactivateToActiveServiceLocked(int reason) { 214 if (DBG) Log.d(TAG, "sendDeactivateToActiveServiceLocked"); 215 if (mActiveService == null) return; 216 Message msg = Message.obtain(null, HostNfcFService.MSG_DEACTIVATED); 217 msg.arg1 = reason; 218 try { 219 mActiveService.send(msg); 220 } catch (RemoteException e) { 221 // Don't care 222 } 223 } 224 bindServiceIfNeededLocked(ComponentName service)225 Messenger bindServiceIfNeededLocked(ComponentName service) { 226 if (DBG) Log.d(TAG, "bindServiceIfNeededLocked"); 227 if (mServiceBound && mServiceName.equals(service)) { 228 Log.d(TAG, "Service already bound."); 229 return mService; 230 } else { 231 Log.d(TAG, "Binding to service " + service); 232 unbindServiceIfNeededLocked(); 233 Intent bindIntent = new Intent(HostNfcFService.SERVICE_INTERFACE); 234 bindIntent.setComponent(service); 235 if (mContext.bindServiceAsUser(bindIntent, mConnection, 236 Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { 237 } else { 238 Log.e(TAG, "Could not bind service."); 239 } 240 return null; 241 } 242 } 243 unbindServiceIfNeededLocked()244 void unbindServiceIfNeededLocked() { 245 if (DBG) Log.d(TAG, "unbindServiceIfNeededLocked"); 246 if (mServiceBound) { 247 Log.d(TAG, "Unbinding from service " + mServiceName); 248 mContext.unbindService(mConnection); 249 mServiceBound = false; 250 mService = null; 251 mServiceName = null; 252 } 253 } 254 findNfcid2(byte[] data)255 String findNfcid2(byte[] data) { 256 if (DBG) Log.d(TAG, "findNfcid2"); 257 if (data == null || data.length < MINIMUM_NFCF_PACKET_LENGTH) { 258 if (DBG) Log.d(TAG, "Data size too small"); 259 return null; 260 } 261 int nfcid2Offset = 2; 262 return bytesToString(data, nfcid2Offset, NFCID2_LENGTH); 263 } 264 265 private ServiceConnection mConnection = new ServiceConnection() { 266 @Override 267 public void onServiceConnected(ComponentName name, IBinder service) { 268 synchronized (mLock) { 269 mService = new Messenger(service); 270 mServiceBound = true; 271 mServiceName = name; 272 Log.d(TAG, "Service bound"); 273 mState = STATE_XFER; 274 // Send pending packet 275 if (mPendingPacket != null) { 276 sendDataToServiceLocked(mService, mPendingPacket); 277 mPendingPacket = null; 278 } 279 } 280 } 281 282 @Override 283 public void onServiceDisconnected(ComponentName name) { 284 synchronized (mLock) { 285 Log.d(TAG, "Service unbound"); 286 mService = null; 287 mServiceBound = false; 288 mServiceName = null; 289 } 290 } 291 }; 292 293 class MessageHandler extends Handler { 294 @Override handleMessage(Message msg)295 public void handleMessage(Message msg) { 296 synchronized(mLock) { 297 if (mActiveService == null) { 298 Log.d(TAG, "Dropping service response message; service no longer active."); 299 return; 300 } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) { 301 Log.d(TAG, "Dropping service response message; service no longer bound."); 302 return; 303 } 304 } 305 if (msg.what == HostNfcFService.MSG_RESPONSE_PACKET) { 306 Bundle dataBundle = msg.getData(); 307 if (dataBundle == null) { 308 return; 309 } 310 byte[] data = dataBundle.getByteArray("data"); 311 if (data == null) { 312 return; 313 } 314 if (data.length == 0) { 315 Log.e(TAG, "Invalid response packet"); 316 return; 317 } 318 if (data.length != (data[0] & 0xff)) { 319 Log.e(TAG, "Invalid response packet"); 320 return; 321 } 322 int state; 323 synchronized(mLock) { 324 state = mState; 325 } 326 if (state == STATE_XFER) { 327 Log.d(TAG, "Sending data"); 328 if (DBG) Log.d(TAG, "data:" + getByteDump(data)); 329 NfcService.getInstance().sendData(data); 330 } else { 331 Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state)); 332 } 333 } 334 } 335 } 336 bytesToString(byte[] bytes, int offset, int length)337 static String bytesToString(byte[] bytes, int offset, int length) { 338 final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; 339 char[] chars = new char[length * 2]; 340 int byteValue; 341 for (int j = 0; j < length; j++) { 342 byteValue = bytes[offset + j] & 0xFF; 343 chars[j * 2] = hexChars[byteValue >>> 4]; 344 chars[j * 2 + 1] = hexChars[byteValue & 0x0F]; 345 } 346 return new String(chars); 347 } 348 getByteDump(final byte[] cmd)349 private String getByteDump(final byte[] cmd) { 350 StringBuffer str = new StringBuffer(""); 351 int letters = 8; 352 int i = 0; 353 354 if (cmd == null) { 355 str.append(" null\n"); 356 return str.toString(); 357 } 358 359 for (; i < cmd.length; i++) { 360 str.append(String.format(" %02X", cmd[i])); 361 if ((i % letters == letters - 1) || (i + 1 == cmd.length)) { 362 str.append("\n"); 363 } 364 } 365 366 return str.toString(); 367 } 368 dump(FileDescriptor fd, PrintWriter pw, String[] args)369 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 370 pw.println("Bound HCE-F services: "); 371 if (mServiceBound) { 372 pw.println(" service: " + mServiceName); 373 } 374 } 375 } 376