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