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