• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.nfc.cardemulation.util;
17 
18 import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V1;
19 import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V2;
20 import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__PROPRIETARY_FRAME_UNKNOWN;
21 
22 import android.annotation.FlaggedApi;
23 import android.annotation.NonNull;
24 import android.nfc.cardemulation.CardEmulation;
25 import android.nfc.cardemulation.PollingFrame;
26 import android.os.SystemClock;
27 import android.sysprop.NfcProperties;
28 import android.util.Log;
29 
30 import com.android.nfc.NfcStatsLog;
31 import com.android.nfc.flags.Flags;
32 
33 import java.util.HashMap;
34 import java.util.Objects;
35 
36 @FlaggedApi(Flags.FLAG_STATSD_CE_EVENTS_FLAG)
37 public class StatsdUtils {
38     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
39     private final String TAG = "StatsdUtils";
40 
41     public static final String SE_NAME_HCE = "HCE";
42     public static final String SE_NAME_HCEF = "HCEF";
43 
44     /** Wrappers for Category values */
45     public static final int CE_UNKNOWN =
46             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__UNKNOWN;
47     /** Successful cases */
48     public static final int CE_HCE_PAYMENT =
49             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_PAYMENT;
50     public static final int CE_HCE_OTHER =
51             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_OTHER;
52     public static final int CE_OFFHOST =
53             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__OFFHOST;
54     public static final int CE_OFFHOST_PAYMENT =
55             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__OFFHOST_PAYMENT;
56     public static final int CE_OFFHOST_OTHER =
57             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__OFFHOST_OTHER;
58     /** NO_ROUTING */
59     public static final int CE_NO_ROUTING =
60             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_NO_ROUTING;
61     /** WRONG_SETTING */
62     public static final int CE_PAYMENT_WRONG_SETTING =
63             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_HCE_PAYMENT_WRONG_SETTING;
64     public static final int CE_OTHER_WRONG_SETTING =
65             NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_HCE_OTHER_WRONG_SETTING;
66     /** DISCONNECTED_BEFORE_BOUND */
67     public static final int CE_PAYMENT_DC_BOUND = NfcStatsLog
68             .NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_HCE_PAYMENT_DISCONNECTED_BEFORE_BOUND;
69     public static final int CE_OTHER_DC_BOUND = NfcStatsLog
70             .NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_HCE_OTHER_DISCONNECTED_BEFORE_BOUND;
71     /** DISCONNECTED_BEFORE_RESPONSE */
72     public static final int CE_PAYMENT_DC_RESPONSE = NfcStatsLog
73             .NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_HCE_PAYMENT_DISCONNECTED_BEFORE_RESPONSE;
74     public static final int CE_OTHER_DC_RESPONSE = NfcStatsLog
75             .NFC_CARDEMULATION_OCCURRED__CATEGORY__FAILED_HCE_OTHER_DISCONNECTED_BEFORE_RESPONSE;
76     /** Wrappers for Category values */
77 
78     public static final int TRIGGER_SOURCE_UNKNOWN =
79             NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__TRIGGER_SOURCE_UNKNOWN;
80     public static final int TRIGGER_SOURCE_WALLET_ROLE_HOLDER =
81             NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__WALLET_ROLE_HOLDER;
82     public static final int TRIGGER_SOURCE_FOREGROUND_APP =
83             NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__FOREGROUND_APP;
84     public static final int TRIGGER_SOURCE_AUTO_TRANSACT =
85             NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__AUTO_TRANSACT;
86 
87     public static final int PROCESSOR_UNKNOWN =
88             NfcStatsLog.NFC_AUTO_TRANSACT_REPORTED__AUTO_TRANSACT_PROCESSOR__PROCESSOR_UNKNOWN;
89     public static final int PROCESSOR_HOST =
90             NfcStatsLog.NFC_AUTO_TRANSACT_REPORTED__AUTO_TRANSACT_PROCESSOR__HOST;
91     public static final int PROCESSOR_NFCC =
92             NfcStatsLog.NFC_AUTO_TRANSACT_REPORTED__AUTO_TRANSACT_PROCESSOR__NFCC;
93 
94     /** Name of SE terminal to log in statsd */
95     private String mSeName = "";
96     /** Timestamp in millis when app binding starts */
97     private long mBindingStartTimeMillis = 0;
98     /** Flag to indicate that the service has not sent the first response */
99     private boolean mWaitingForFirstResponse = false;
100     /** Current transaction's category to log in statsd */
101     private String mTransactionCategory = CardEmulation.EXTRA_CATEGORY;
102     /** Current transaction's uid to log in statsd */
103     private int mTransactionUid = -1;
104     /** Shared context between instances of StatsdUtils */
105     @NonNull
106     private StatsdUtilsContext mStatsdUtilsContext;
107 
108     private static final byte FRAME_HEADER_ECP = 0x6A;
109     private static final byte FRAME_ECP_V1 = 0x01;
110     private static final byte FRAME_ECP_V2 = 0x02;
111     private static final int FRAME_ECP_MIN_SIZE = 5;
112 
113     private static final int NO_GAIN_INFORMATION = -1;
114     private int mLastGainLevel = NO_GAIN_INFORMATION;
115 
116     /** Result constants for statsd usage */
117     static enum StatsdResult {
118         SUCCESS,
119         NO_ROUTING_FOR_AID,
120         WRONG_APP_AND_DEVICE_SETTINGS,
121         DISCONNECTED_BEFORE_BOUND,
122         DISCONNECTED_BEFORE_RESPONSE
123     }
124 
StatsdUtils(String seName, @NonNull StatsdUtilsContext statsdUtilsContext)125     public StatsdUtils(String seName, @NonNull StatsdUtilsContext statsdUtilsContext) {
126         this(statsdUtilsContext);
127         mSeName = seName;
128 
129         // HCEF has no category, default it to PAYMENT category to record every call
130         if (seName.equals(SE_NAME_HCEF)) mTransactionCategory = CardEmulation.CATEGORY_PAYMENT;
131     }
132 
StatsdUtils(@onNull StatsdUtilsContext statsdUtilsContext)133     public StatsdUtils(@NonNull StatsdUtilsContext statsdUtilsContext) {
134         mStatsdUtilsContext = statsdUtilsContext;
135     }
136 
resetCardEmulationEvent()137     private void resetCardEmulationEvent() {
138         // Reset mTransactionCategory value to prevent accidental triggers in general
139         // except for HCEF, which is always intentional because it only works in foreground
140         if (!mSeName.equals(SE_NAME_HCEF)) mTransactionCategory = CardEmulation.EXTRA_CATEGORY;
141         mBindingStartTimeMillis = 0;
142         mWaitingForFirstResponse = false;
143         mTransactionUid = -1;
144     }
145 
getCardEmulationStatsdCategory( StatsdResult transactionResult, String transactionCategory)146     private int getCardEmulationStatsdCategory(
147             StatsdResult transactionResult, String transactionCategory) {
148         switch (transactionResult) {
149             case SUCCESS:
150                 switch (transactionCategory) {
151                     case CardEmulation.CATEGORY_PAYMENT:
152                         return CE_HCE_PAYMENT;
153                     case CardEmulation.CATEGORY_OTHER:
154                         return CE_HCE_OTHER;
155                     default:
156                         return CE_UNKNOWN;
157                 }
158 
159             case NO_ROUTING_FOR_AID:
160                 return CE_NO_ROUTING;
161 
162             case WRONG_APP_AND_DEVICE_SETTINGS:
163                 switch (transactionCategory) {
164                     case CardEmulation.CATEGORY_PAYMENT:
165                         return CE_PAYMENT_WRONG_SETTING;
166                     case CardEmulation.CATEGORY_OTHER:
167                         return CE_OTHER_WRONG_SETTING;
168                     default:
169                         return CE_UNKNOWN;
170                 }
171 
172             case DISCONNECTED_BEFORE_BOUND:
173                 switch (transactionCategory) {
174                     case CardEmulation.CATEGORY_PAYMENT:
175                         return CE_PAYMENT_DC_BOUND;
176                     case CardEmulation.CATEGORY_OTHER:
177                         return CE_OTHER_DC_BOUND;
178                     default:
179                         return CE_UNKNOWN;
180                 }
181 
182             case DISCONNECTED_BEFORE_RESPONSE:
183                 switch (transactionCategory) {
184                     case CardEmulation.CATEGORY_PAYMENT:
185                         return CE_PAYMENT_DC_RESPONSE;
186                     case CardEmulation.CATEGORY_OTHER:
187                         return CE_OTHER_DC_RESPONSE;
188                     default:
189                         return CE_UNKNOWN;
190                 }
191         }
192         return CE_UNKNOWN;
193     }
194 
logCardEmulationEvent(int statsdCategory)195     void logCardEmulationEvent(int statsdCategory) {
196         NfcStatsLog.write(
197                 NfcStatsLog.NFC_CARDEMULATION_OCCURRED, statsdCategory, mSeName, mTransactionUid);
198         resetCardEmulationEvent();
199     }
200 
logErrorEvent(int errorType, int nciCmd, int ntfStatusCode)201     public void logErrorEvent(int errorType, int nciCmd, int ntfStatusCode) {
202         NfcStatsLog.write(NfcStatsLog.NFC_ERROR_OCCURRED, errorType, nciCmd, ntfStatusCode);
203     }
204 
logErrorEvent(int errorType)205     public void logErrorEvent(int errorType) {
206         logErrorEvent(errorType, 0, 0);
207     }
208 
setCardEmulationEventCategory(String category)209     public void setCardEmulationEventCategory(String category) {
210         mTransactionCategory = category;
211     }
212 
setCardEmulationEventUid(int uid)213     public void setCardEmulationEventUid(int uid) {
214         mTransactionUid = uid;
215     }
216 
notifyCardEmulationEventWaitingForResponse()217     public void notifyCardEmulationEventWaitingForResponse() {
218         mWaitingForFirstResponse = true;
219     }
220 
notifyCardEmulationEventResponseReceived()221     public void notifyCardEmulationEventResponseReceived() {
222         mWaitingForFirstResponse = false;
223     }
224 
notifyCardEmulationEventWaitingForService()225     public void notifyCardEmulationEventWaitingForService() {
226         mBindingStartTimeMillis = SystemClock.elapsedRealtime();
227     }
228 
notifyCardEmulationEventServiceBound()229     public void notifyCardEmulationEventServiceBound() {
230         int bindingLimitMillis = 500;
231         if (mBindingStartTimeMillis > 0) {
232             long bindingElapsedTimeMillis = SystemClock.elapsedRealtime() - mBindingStartTimeMillis;
233             if (DBG) {
234                 Log.d(TAG, "notifyCardEmulationEventServiceBound: binding took "
235                         + bindingElapsedTimeMillis + " millis");
236             }
237             if (bindingElapsedTimeMillis >= bindingLimitMillis) {
238                 logErrorEvent(NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__HCE_LATE_BINDING);
239             }
240             mBindingStartTimeMillis = 0;
241         }
242     }
243 
logCardEmulationWrongSettingEvent()244     public void logCardEmulationWrongSettingEvent() {
245         int statsdCategory =
246                 getCardEmulationStatsdCategory(
247                         StatsdResult.WRONG_APP_AND_DEVICE_SETTINGS, mTransactionCategory);
248         logCardEmulationEvent(statsdCategory);
249     }
250 
logCardEmulationNoRoutingEvent()251     public void logCardEmulationNoRoutingEvent() {
252         int statsdCategory =
253                 getCardEmulationStatsdCategory(
254                         StatsdResult.NO_ROUTING_FOR_AID, mTransactionCategory);
255         logCardEmulationEvent(statsdCategory);
256     }
257 
logCardEmulationDeactivatedEvent()258     public void logCardEmulationDeactivatedEvent() {
259         if (mTransactionCategory.equals(CardEmulation.EXTRA_CATEGORY)) {
260             // Skip deactivation calls without select apdu
261             resetCardEmulationEvent();
262             return;
263         }
264 
265         StatsdResult transactionResult;
266         if (mBindingStartTimeMillis > 0) {
267             transactionResult = StatsdResult.DISCONNECTED_BEFORE_BOUND;
268         } else if (mWaitingForFirstResponse) {
269             transactionResult = StatsdResult.DISCONNECTED_BEFORE_RESPONSE;
270         } else {
271             transactionResult = StatsdResult.SUCCESS;
272         }
273         int statsdCategory =
274                 getCardEmulationStatsdCategory(transactionResult, mTransactionCategory);
275         logCardEmulationEvent(statsdCategory);
276     }
277 
logCardEmulationOffhostEvent(String seName)278     public void logCardEmulationOffhostEvent(String seName) {
279         mSeName = seName;
280 
281         int statsdCategory;
282         switch (mTransactionCategory) {
283             case CardEmulation.CATEGORY_PAYMENT:
284                 statsdCategory = CE_OFFHOST_PAYMENT;
285                 break;
286             case CardEmulation.CATEGORY_OTHER:
287                 statsdCategory = CE_OFFHOST_OTHER;
288                 break;
289             default:
290                 statsdCategory = CE_OFFHOST;
291         }
292         logCardEmulationEvent(statsdCategory);
293     }
294 
logFieldChanged(boolean isOn, int fieldStrength)295     public void logFieldChanged(boolean isOn, int fieldStrength) {
296         NfcStatsLog.write(NfcStatsLog.NFC_FIELD_CHANGED,
297                 isOn ? NfcStatsLog.NFC_FIELD_CHANGED__FIELD_STATUS__FIELD_ON
298                         : NfcStatsLog.NFC_FIELD_CHANGED__FIELD_STATUS__FIELD_OFF, fieldStrength);
299 
300         if (!isOn) {
301             mLastGainLevel = NO_GAIN_INFORMATION;
302         }
303     }
304 
305     /**
306      * Set the trigger source of the next observe mode state change. Use this when the source of the
307      * change is not known at the time of the change. This value will be cleared after the next
308      * call to logObserveModeStateChanged().
309      */
setNextObserveModeTriggerSource(int triggerSource)310     public void setNextObserveModeTriggerSource(int triggerSource) {
311         mStatsdUtilsContext.setObserveModeTriggerSource(triggerSource);
312     }
313 
314     /**
315      * Log when observe mode is enabled or disabled.
316      *
317      * @param enabled       true if observe mode is enabled, false if it is disabled
318      * @param triggerSource the source of the change, if known. This value will be ignored if
319      *                      setNextObserveModeTriggerSource() has been called.
320      * @param latencyMs     the amount of time that it took to enable or disable observe mode in
321      *                      milliseconds.
322      */
logObserveModeStateChanged(boolean enabled, int triggerSource, int latencyMs)323     public void logObserveModeStateChanged(boolean enabled, int triggerSource, int latencyMs) {
324         Integer overrideTriggerSource = mStatsdUtilsContext.getObserveModeTriggerSource();
325 
326         NfcStatsLog.write(NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED,
327                 enabled ? NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__STATE__OBSERVE_MODE_ENABLED
328                         : NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__STATE__OBSERVE_MODE_DISABLED,
329                 overrideTriggerSource != null ? overrideTriggerSource : triggerSource, latencyMs);
330         mStatsdUtilsContext.setObserveModeTriggerSource(null);
331     }
332 
333     /**
334      * Log when the exit frame table is changed.
335      *
336      * @param tableSize the number of entries in the exit frame table
337      * @param timeoutMs the timeout, in milliseconds, chosen to restore observe mode
338      */
logExitFrameTableChanged(int tableSize, int timeoutMs)339     public void logExitFrameTableChanged(int tableSize, int timeoutMs) {
340         NfcStatsLog.write(NfcStatsLog.NFC_EXIT_FRAME_TABLE_CHANGED, tableSize, timeoutMs);
341     }
342 
logAutoTransactReported(int processor, byte[] data)343     public void logAutoTransactReported(int processor, byte[] data) {
344       NfcStatsLog.write(NfcStatsLog.NFC_AUTO_TRANSACT_REPORTED, processor, data.length,
345           getFrameType(data));
346     }
347 
348     private final HashMap<String, PollingFrameLog> pollingFrameMap = new HashMap<>();
349 
tallyPollingFrame(String frameDataHex, PollingFrame frame)350     public void tallyPollingFrame(String frameDataHex, PollingFrame frame) {
351         int type = frame.getType();
352 
353         int gainLevel = frame.getVendorSpecificGain();
354         if (gainLevel != -1) {
355             if (mLastGainLevel != gainLevel) {
356                 logFieldChanged(true, gainLevel);
357                 mLastGainLevel = gainLevel;
358             }
359         }
360 
361         if (type == PollingFrame.POLLING_LOOP_TYPE_UNKNOWN) {
362             byte[] data = frame.getData();
363 
364             PollingFrameLog log = pollingFrameMap.getOrDefault(frameDataHex, null);
365 
366             if (log == null) {
367                 PollingFrameLog frameLog = new PollingFrameLog(data);
368 
369                 pollingFrameMap.put(frameDataHex, frameLog);
370             } else {
371                 log.repeatCount++;
372             }
373         }
374     }
375 
logPollingFrames()376     public void logPollingFrames() {
377         for (PollingFrameLog log : pollingFrameMap.values()) {
378             writeToStatsd(log);
379         }
380         pollingFrameMap.clear();
381     }
382 
getFrameType(byte[] data)383     protected static int getFrameType(byte[] data) {
384         int frameType =
385           NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__PROPRIETARY_FRAME_UNKNOWN;
386 
387         if (data != null && data.length >= FRAME_ECP_MIN_SIZE && data[0] == FRAME_HEADER_ECP) {
388             frameType = switch (data[1]) {
389                 case FRAME_ECP_V1 ->
390                         NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V1;
391                 case FRAME_ECP_V2 ->
392                         NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V2;
393                 default -> frameType;
394             };
395         }
396         return frameType;
397     }
398 
writeToStatsd(PollingFrameLog frameLog)399     protected void writeToStatsd(PollingFrameLog frameLog) {
400         NfcStatsLog.write(NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED,
401                 frameLog.frameType,
402                 frameLog.repeatCount);
403     }
404 
405     protected static class PollingFrameLog {
406         int repeatCount = 1;
407         final int frameType;
408 
PollingFrameLog(byte[] data)409         public PollingFrameLog(byte[] data) {
410             frameType = getFrameType(data);
411         }
412 
413         @Override
equals(Object o)414         public boolean equals(Object o) {
415             if (this == o) return true;
416             if (!(o instanceof PollingFrameLog)) return false;
417             PollingFrameLog that = (PollingFrameLog) o;
418             return repeatCount == that.repeatCount && frameType == that.frameType;
419         }
420 
421         @Override
hashCode()422         public int hashCode() {
423             return Objects.hash(repeatCount, frameType);
424         }
425 
426         @Override
toString()427         public String toString() {
428             return "PollingFrameLog{" +
429                     "repeatCount=" + repeatCount +
430                     ", frameType=" + frameType +
431                     '}';
432         }
433     }
434 }
435