• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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