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