1 /*
2  * Copyright (C) 2024 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.emulator;
17 
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.nfc.NfcAdapter;
25 import android.nfc.cardemulation.PollingFrame;
26 import android.os.Bundle;
27 import android.util.Log;
28 
29 import com.android.nfc.service.PollingLoopService;
30 
31 import java.util.ArrayDeque;
32 import java.util.HexFormat;
33 import java.util.List;
34 import java.util.Queue;
35 
36 public class PollingLoopEmulatorActivity extends BaseEmulatorActivity {
37     private static final String TAG = "PollingLoopActivity";
38     int mNfcTech = 0;
39     int mNfcACount = 0;
40     int mNfcBCount = 0;
41     int mNfcOnCount = 0;
42     int mNfcOffCount = 0;
43     String mCustomFrame = null;
44 
45     // Number of loops to track in queue
46     public static final int NUM_LOOPS = 4;
47     public static final String NFC_TECH_KEY = "NFC_TECH";
48     public static final String NFC_CUSTOM_FRAME_KEY = "NFC_CUSTOM_FRAME";
49     public static final String SEEN_CORRECT_POLLING_LOOP_ACTION =
50             PACKAGE_NAME + ".SEEN_CORRECT_POLLING_LOOP_ACTION";
51     private boolean mSentBroadcast = false;
52 
53     // Keeps track of last mCapacity PollingFrames
54     private Queue<PollingFrame> mQueue = new ArrayDeque<PollingFrame>();
55 
56     private int mCapacity;
57 
58     private int mLoopSize;
59 
60     @Override
onCreate(Bundle savedInstanceState)61     protected void onCreate(Bundle savedInstanceState) {
62         super.onCreate(savedInstanceState);
63         setupServices(PollingLoopService.COMPONENT);
64     }
65 
66     @Override
onResume()67     protected void onResume() {
68         super.onResume();
69         IntentFilter filter = new IntentFilter(PollingLoopService.POLLING_FRAME_ACTION);
70         registerReceiver(mFieldStateReceiver, filter, RECEIVER_EXPORTED);
71         mNfcTech = getIntent().getIntExtra(NFC_TECH_KEY, NfcAdapter.FLAG_READER_NFC_A);
72         ComponentName serviceName =
73                 new ComponentName(this.getApplicationContext(), PollingLoopService.class);
74         mCardEmulation.setShouldDefaultToObserveModeForService(serviceName, true);
75 
76         mCustomFrame = getIntent().getStringExtra(NFC_CUSTOM_FRAME_KEY);
77         boolean isPreferredServiceSet = mCardEmulation.setPreferredService(this, serviceName);
78         waitForPreferredService();
79         waitForObserveModeEnabled(true);
80 
81         mNfcACount = 0;
82         mNfcBCount = 0;
83         mNfcOnCount = 0;
84         mNfcOffCount = 0;
85         mSentBroadcast = false;
86         mQueue = new ArrayDeque<PollingFrame>();
87 
88         // A-B loop: 0-A-B-X
89         // A loop: 0-A-X
90         // B loop: 0-B-X
91         mLoopSize =
92                 mNfcTech == (NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B) ? 4 : 3;
93         mCapacity = mLoopSize * NUM_LOOPS;
94         Log.d(
95                 TAG,
96                 "onResume. mNfcTech: "
97                         + mNfcTech
98                         + ", isPreferredServiceSet: "
99                         + isPreferredServiceSet);
100     }
101 
102     @Override
onPause()103     public void onPause() {
104         super.onPause();
105         Log.e(TAG, "onPause");
106         unregisterReceiver(mFieldStateReceiver);
107         mCardEmulation.unsetPreferredService(this);
108     }
109 
110     @Override
onApduSequenceComplete(ComponentName component, long duration)111     protected void onApduSequenceComplete(ComponentName component, long duration) {
112         if (component.equals(PollingLoopService.COMPONENT)) {
113             setTestPassed();
114         }
115     }
116 
117     @Override
getPreferredServiceComponent()118     public ComponentName getPreferredServiceComponent() {
119         return PollingLoopService.COMPONENT;
120     }
121 
processPollingFrames(List<PollingFrame> frames)122     void processPollingFrames(List<PollingFrame> frames) {
123         Log.d(TAG, "processPollingFrames of size " + frames.size());
124         for (PollingFrame frame : frames) {
125             processPollingFrame(frame);
126         }
127         Log.d(
128                 TAG,
129                 "seenCorrectPollingLoop?: " + seenCorrectPollingLoop()
130                         + ", mNfcACount: "
131                         + mNfcACount
132                         + ", mNfcBCount: "
133                         + mNfcBCount
134                         + ", mNfcOffCount: "
135                         + mNfcOffCount
136                         + ", mNfcOnCount: "
137                         + mNfcOnCount
138                         + ", mNfcTech: "
139                         + mNfcTech);
140 
141         if (seenCorrectPollingLoop() && !mSentBroadcast) {
142             Intent intent = new Intent(SEEN_CORRECT_POLLING_LOOP_ACTION);
143             sendBroadcast(intent);
144             mSentBroadcast = true;
145             Log.d(TAG, "Correct polling loop seen. Sent broadcast");
146         }
147     }
148 
seenCorrectPollingLoop()149     private boolean seenCorrectPollingLoop() {
150         if (mCustomFrame != null) {
151             return false;
152         }
153         if (mNfcTech == NfcAdapter.FLAG_READER_NFC_A) {
154             if (mNfcACount >= 3
155                     && mNfcBCount == 0
156                     && mNfcOnCount >= 3
157                     && mNfcOffCount >= 3
158                     && Math.abs(mNfcOffCount - mNfcOnCount) <= 1) {
159                 return true;
160             }
161         } else if (mNfcTech == NfcAdapter.FLAG_READER_NFC_B) {
162             if (mNfcBCount >= 3
163                     && mNfcACount == 0
164                     && mNfcOnCount >= 3
165                     && mNfcOffCount >= 3
166                     && Math.abs(mNfcOffCount - mNfcOnCount) <= 1) {
167                 return true;
168             }
169         } else if (mNfcTech == (NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B)) {
170             if (mNfcACount >= 3
171                     && mNfcBCount >= 3
172                     && mNfcOnCount >= 3
173                     && mNfcOffCount >= 3
174                     && Math.abs(mNfcOffCount - mNfcOnCount) <= 1) {
175                 return true;
176             }
177         }
178         return false;
179     }
180 
processPollingFrame(PollingFrame frame)181     void processPollingFrame(PollingFrame frame) {
182         int type = frame.getType();
183         Log.d(TAG, "processPollingFrame: " + (char) (frame.getType()));
184 
185         if (mQueue.size() == mCapacity) {
186             removeFirstElement();
187         }
188         mQueue.add(frame);
189         switch (type) {
190             case PollingFrame.POLLING_LOOP_TYPE_A:
191                 ++mNfcACount;
192                 break;
193             case PollingFrame.POLLING_LOOP_TYPE_B:
194                 ++mNfcBCount;
195                 break;
196             case PollingFrame.POLLING_LOOP_TYPE_ON:
197                 ++mNfcOnCount;
198                 break;
199             case PollingFrame.POLLING_LOOP_TYPE_OFF:
200                 ++mNfcOffCount;
201                 break;
202             case PollingFrame.POLLING_LOOP_TYPE_UNKNOWN:
203                 Log.e(TAG, "got custom frame: " + HexFormat.of().formatHex(frame.getData()));
204                 if (mCustomFrame != null && !seenCorrectPollingLoop()) {
205                     byte[] data = frame.getData();
206                     if (mCustomFrame.equals(HexFormat.of().formatHex(data))) {
207                         Intent intent = new Intent(SEEN_CORRECT_POLLING_LOOP_ACTION);
208                         sendBroadcast(intent);
209                         Log.d(TAG, "Correct custom polling frame seen. Sent broadcast");
210                     }
211                 }
212                 break;
213         }
214     }
215 
removeFirstElement()216     private void removeFirstElement() {
217         PollingFrame frame = mQueue.poll();
218         if (frame == null) {
219             return;
220         }
221         switch (frame.getType()) {
222             case PollingFrame.POLLING_LOOP_TYPE_A:
223                 --mNfcACount;
224                 break;
225             case PollingFrame.POLLING_LOOP_TYPE_B:
226                 --mNfcBCount;
227                 break;
228             case PollingFrame.POLLING_LOOP_TYPE_ON:
229                 --mNfcOnCount;
230                 break;
231             case PollingFrame.POLLING_LOOP_TYPE_OFF:
232                 --mNfcOffCount;
233                 break;
234         }
235     }
236 
237     final BroadcastReceiver mFieldStateReceiver =
238             new BroadcastReceiver() {
239                 @Override
240                 public void onReceive(Context context, Intent intent) {
241                     String action = intent.getAction();
242                     if (action.equals(PollingLoopService.POLLING_FRAME_ACTION)) {
243                         processPollingFrames(
244                                 intent.getParcelableArrayListExtra(
245                                         PollingLoopService.POLLING_FRAME_EXTRA,
246                                         PollingFrame.class));
247                     }
248                 }
249             };
250 }
251