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