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