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