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