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