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