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         waitForService();
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
getPreferredServiceComponent()111     public ComponentName getPreferredServiceComponent() {
112         return PollingLoopService.COMPONENT;
113     }
114 
processPollingFrames(List<PollingFrame> frames)115     void processPollingFrames(List<PollingFrame> frames) {
116         Log.d(TAG, "processPollingFrames of size " + frames.size());
117         for (PollingFrame frame : frames) {
118             processPollingFrame(frame);
119         }
120         Log.d(
121                 TAG,
122                 "seenCorrectPollingLoop?: " + seenCorrectPollingLoop()
123                         + ", mNfcACount: "
124                         + mNfcACount
125                         + ", mNfcBCount: "
126                         + mNfcBCount
127                         + ", mNfcOffCount: "
128                         + mNfcOffCount
129                         + ", mNfcOnCount: "
130                         + mNfcOnCount
131                         + ", mNfcTech: "
132                         + mNfcTech);
133 
134         if (seenCorrectPollingLoop() && !mSentBroadcast) {
135             Intent intent = new Intent(SEEN_CORRECT_POLLING_LOOP_ACTION);
136             sendBroadcast(intent);
137             mSentBroadcast = true;
138             Log.d(TAG, "Correct polling loop seen. Sent broadcast");
139         }
140     }
141 
seenCorrectPollingLoop()142     private boolean seenCorrectPollingLoop() {
143         if (mCustomFrame != null) {
144             return false;
145         }
146         if (mNfcTech == NfcAdapter.FLAG_READER_NFC_A) {
147             if (mNfcACount >= 3
148                     && mNfcBCount == 0
149                     && mNfcOnCount >= 3
150                     && mNfcOffCount >= 3
151                     && Math.abs(mNfcOffCount - mNfcOnCount) <= 1) {
152                 return true;
153             }
154         } else if (mNfcTech == NfcAdapter.FLAG_READER_NFC_B) {
155             if (mNfcBCount >= 3
156                     && mNfcACount == 0
157                     && mNfcOnCount >= 3
158                     && mNfcOffCount >= 3
159                     && Math.abs(mNfcOffCount - mNfcOnCount) <= 1) {
160                 return true;
161             }
162         } else if (mNfcTech == (NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B)) {
163             if (mNfcACount >= 3
164                     && mNfcBCount >= 3
165                     && mNfcOnCount >= 3
166                     && mNfcOffCount >= 3
167                     && Math.abs(mNfcOffCount - mNfcOnCount) <= 1) {
168                 return true;
169             }
170         }
171         return false;
172     }
173 
processPollingFrame(PollingFrame frame)174     void processPollingFrame(PollingFrame frame) {
175         int type = frame.getType();
176         Log.d(TAG, "processPollingFrame: " + (char) (frame.getType()));
177 
178         if (mQueue.size() == mCapacity) {
179             removeFirstElement();
180         }
181         mQueue.add(frame);
182         switch (type) {
183             case PollingFrame.POLLING_LOOP_TYPE_A:
184                 ++mNfcACount;
185                 break;
186             case PollingFrame.POLLING_LOOP_TYPE_B:
187                 ++mNfcBCount;
188                 break;
189             case PollingFrame.POLLING_LOOP_TYPE_ON:
190                 ++mNfcOnCount;
191                 break;
192             case PollingFrame.POLLING_LOOP_TYPE_OFF:
193                 ++mNfcOffCount;
194                 break;
195             case PollingFrame.POLLING_LOOP_TYPE_UNKNOWN:
196                 Log.e(TAG, "got custom frame: " + HexFormat.of().formatHex(frame.getData()));
197                 if (mCustomFrame != null && !seenCorrectPollingLoop()) {
198                     byte[] data = frame.getData();
199                     if (mCustomFrame.equals(HexFormat.of().formatHex(data))) {
200                         Intent intent = new Intent(SEEN_CORRECT_POLLING_LOOP_ACTION);
201                         sendBroadcast(intent);
202                         Log.d(TAG, "Correct custom polling frame seen. Sent broadcast");
203                     }
204                 }
205                 break;
206         }
207     }
208 
removeFirstElement()209     private void removeFirstElement() {
210         PollingFrame frame = mQueue.poll();
211         if (frame == null) {
212             return;
213         }
214         switch (frame.getType()) {
215             case PollingFrame.POLLING_LOOP_TYPE_A:
216                 --mNfcACount;
217                 break;
218             case PollingFrame.POLLING_LOOP_TYPE_B:
219                 --mNfcBCount;
220                 break;
221             case PollingFrame.POLLING_LOOP_TYPE_ON:
222                 --mNfcOnCount;
223                 break;
224             case PollingFrame.POLLING_LOOP_TYPE_OFF:
225                 --mNfcOffCount;
226                 break;
227         }
228     }
229 
230     final BroadcastReceiver mFieldStateReceiver =
231             new BroadcastReceiver() {
232                 @Override
233                 public void onReceive(Context context, Intent intent) {
234                     String action = intent.getAction();
235                     if (action.equals(PollingLoopService.POLLING_FRAME_ACTION)) {
236                         processPollingFrames(
237                                 intent.getParcelableArrayListExtra(
238                                         PollingLoopService.POLLING_FRAME_EXTRA,
239                                         PollingFrame.class));
240                     }
241                 }
242             };
243 }
244