1 /* 2 * Copyright (C) 2020 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 17 package com.android.cts.verifier.nfc.offhost; 18 19 import android.app.AlertDialog; 20 import android.content.Intent; 21 import android.content.SharedPreferences; 22 import android.nfc.NfcAdapter; 23 import android.nfc.NfcAdapter.ReaderCallback; 24 import android.nfc.Tag; 25 import android.nfc.tech.IsoDep; 26 import android.nfc.tech.NfcA; 27 import android.nfc.tech.NfcB; 28 import android.os.Bundle; 29 import android.os.Parcelable; 30 import android.util.Log; 31 import android.view.View; 32 import android.widget.AdapterView; 33 import android.widget.AdapterView.OnItemSelectedListener; 34 import android.widget.ArrayAdapter; 35 import android.widget.Spinner; 36 import android.widget.TextView; 37 38 import com.android.cts.verifier.PassFailButtons; 39 import com.android.cts.verifier.R; 40 import com.android.cts.verifier.nfc.hce.CommandApdu; 41 import com.android.cts.verifier.nfc.hce.HceUtils; 42 43 import java.io.IOException; 44 import java.util.Arrays; 45 46 public class SimpleOffhostReaderActivity extends PassFailButtons.Activity implements ReaderCallback, 47 OnItemSelectedListener { 48 public static final String PREFS_NAME = "OffhostTypePrefs"; 49 50 public static final String TAG = "SimpleOffhostReaderActivity"; 51 public static final String EXTRA_APDUS = "apdus"; 52 public static final String EXTRA_RESPONSES = "responses"; 53 public static final String EXTRA_LABEL = "label"; 54 public static final String EXTRA_DESELECT = "deselect"; 55 56 NfcAdapter mAdapter; 57 CommandApdu[] mApdus; 58 String[] mResponses; 59 String mLabel; 60 boolean mDeselect; 61 boolean mIsTypeB; 62 63 TextView mTextView; 64 Spinner mSpinner; 65 SharedPreferences mPrefs; 66 67 @Override onCreate(Bundle savedInstanceState)68 protected void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 setContentView(R.layout.nfc_hce_reader); 71 setPassFailButtonClickListeners(); 72 getPassButton().setEnabled(false); 73 74 mLabel = getIntent().getStringExtra(EXTRA_LABEL); 75 mDeselect = getIntent().getBooleanExtra(EXTRA_DESELECT, false); 76 setTitle(mLabel); 77 78 mAdapter = NfcAdapter.getDefaultAdapter(this); 79 mTextView = (TextView) findViewById(R.id.text); 80 mTextView.setTextSize(12.0f); 81 mTextView.setText(R.string.nfc_offhost_uicc_type_selection); 82 83 Spinner spinner = (Spinner) findViewById(R.id.type_ab_selection); 84 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, 85 R.array.nfc_types_array, android.R.layout.simple_spinner_item); 86 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 87 spinner.setAdapter(adapter); 88 spinner.setOnItemSelectedListener(this); 89 90 mPrefs = getSharedPreferences(PREFS_NAME, 0); 91 mIsTypeB = mPrefs.getBoolean("typeB", false); 92 if (mIsTypeB) { 93 spinner.setSelection(1); 94 } 95 } 96 97 @Override onResume()98 protected void onResume() { 99 super.onResume(); 100 mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A | 101 NfcAdapter.FLAG_READER_NFC_BARCODE | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null); 102 Intent intent = getIntent(); 103 Parcelable[] apdus = intent.getParcelableArrayExtra(EXTRA_APDUS); 104 if (apdus != null) { 105 mApdus = new CommandApdu[apdus.length]; 106 for (int i = 0; i < apdus.length; i++) { 107 mApdus[i] = (CommandApdu) apdus[i]; 108 } 109 } else { 110 mApdus = null; 111 } 112 mResponses = intent.getStringArrayExtra(EXTRA_RESPONSES); 113 } 114 115 @Override onTagDiscovered(Tag tag)116 public void onTagDiscovered(Tag tag) { 117 final StringBuilder sb = new StringBuilder(); 118 long startTime = 0; 119 boolean success = true; 120 int count = 0; 121 122 try { 123 if (mDeselect) { 124 mIsTypeB = mPrefs.getBoolean("typeB", false); 125 // Use FrameRF for deselect test case 126 if (mIsTypeB) { 127 NfcB nfcb = NfcB.get(tag); 128 if (nfcb == null) { 129 // TODO dialog box 130 return; 131 } 132 byte[] tagId = tag.getId(); 133 String tagIdString = HceUtils.getHexBytes("", tagId); 134 nfcb.connect(); 135 startTime = System.currentTimeMillis(); 136 137 //ATTRIB 138 int tagIdLen = tagId.length; 139 if (tagIdLen != 4) { 140 // NFCID0 should be 4 bytes 141 return; 142 } 143 byte[] attrib = new byte[tagIdLen + 5]; 144 attrib[0] = (byte) 0x1d; 145 for (int i = 0; i < tagIdLen; i++) { 146 attrib[1 + i] = tagId[i]; 147 } 148 attrib[tagIdLen + 1] = 0x00; 149 attrib[tagIdLen + 2] = 0x08; 150 attrib[tagIdLen + 3] = 0x01; 151 attrib[tagIdLen + 4] = 0x00; 152 nfcb.transceive(attrib); 153 154 count = 0; 155 for (CommandApdu apdu: mApdus) { 156 sb.append("Request APDU:\n"); 157 sb.append(apdu.getApdu() + "\n\n"); 158 long apduStartTime = System.currentTimeMillis(); 159 byte[] response = 160 nfcb.transceive(HceUtils.hexStringToBytes(apdu.getApdu())); 161 long apduEndTime = System.currentTimeMillis(); 162 if (!responseCheck(sb, response, count, apduStartTime, apduEndTime)) { 163 success = false; 164 break; 165 } 166 count++; 167 } 168 nfcb.transceive(HceUtils.hexStringToBytes("C2")); 169 } else { 170 NfcA nfca = NfcA.get(tag); 171 if (nfca == null) { 172 // TODO dialog box 173 return; 174 } 175 nfca.connect(); 176 nfca.setTimeout(5000); 177 startTime = System.currentTimeMillis(); 178 // RATS 179 nfca.transceive(HceUtils.hexStringToBytes("E080")); 180 181 count = 0; 182 for (CommandApdu apdu: mApdus) { 183 sb.append("Request APDU:\n"); 184 sb.append(apdu.getApdu() + "\n\n"); 185 long apduStartTime = System.currentTimeMillis(); 186 byte[] response = 187 nfca.transceive(HceUtils.hexStringToBytes(apdu.getApdu())); 188 long apduEndTime = System.currentTimeMillis(); 189 if (!responseCheck(sb, response, count, apduStartTime, apduEndTime)) { 190 success = false; 191 break; 192 } 193 count++; 194 } 195 // S-block DESELECT 196 nfca.transceive(HceUtils.hexStringToBytes("C2")); 197 } 198 mAdapter.disableReaderMode(this); 199 } else { 200 IsoDep isoDep = IsoDep.get(tag); 201 if (isoDep == null) { 202 // TODO dialog box 203 return; 204 } 205 isoDep.connect(); 206 isoDep.setTimeout(5000); 207 startTime = System.currentTimeMillis(); 208 209 count = 0; 210 for (CommandApdu apdu: mApdus) { 211 sb.append("Request APDU:\n"); 212 sb.append(apdu.getApdu() + "\n\n"); 213 long apduStartTime = System.currentTimeMillis(); 214 byte[] response = isoDep.transceive(HceUtils.hexStringToBytes(apdu.getApdu())); 215 long apduEndTime = System.currentTimeMillis(); 216 if (!responseCheck(sb, response, count, apduStartTime, apduEndTime)) { 217 success = false; 218 break; 219 } 220 count++; 221 } 222 } 223 224 if (success) { 225 sb.insert(0, "Total APDU exchange time: " + 226 Long.toString(System.currentTimeMillis() - startTime) + " ms.\n\n"); 227 runOnUiThread(new Runnable() { 228 @Override 229 public void run() { 230 mTextView.setText(sb.toString()); 231 getPassButton().setEnabled(true); 232 } 233 }); 234 } else { 235 sb.insert(0, "FAIL. Total APDU exchange time: " + 236 Long.toString(System.currentTimeMillis() - startTime) + " ms.\n\n"); 237 runOnUiThread(new Runnable() { 238 @Override 239 public void run() { 240 mTextView.setText(sb.toString()); 241 AlertDialog.Builder builder = new AlertDialog.Builder(SimpleOffhostReaderActivity.this); 242 builder.setTitle("Test failed"); 243 builder.setMessage("An unexpected response APDU was received, or no APDUs were received at all."); 244 builder.setPositiveButton("OK", null); 245 builder.show(); 246 } 247 }); 248 } 249 } catch (IOException e) { 250 sb.insert(0, "Error while reading: (did you keep the devices in range?)\nPlease try again\n."); 251 runOnUiThread(new Runnable() { 252 @Override 253 public void run() { 254 mTextView.setText(sb.toString()); 255 } 256 }); 257 } finally { 258 } 259 } 260 responseCheck(StringBuilder sb, byte[] response, int count, long apduStartTime, long apduEndTime)261 private boolean responseCheck(StringBuilder sb, byte[] response, int count, 262 long apduStartTime, long apduEndTime) { 263 sb.append("Response APDU (in " + Long.toString(apduEndTime - apduStartTime) 264 + " ms):\n"); 265 sb.append(HceUtils.getHexBytes(null, response)); 266 sb.append("\n\n\n"); 267 boolean wildCard = "*".equals(mResponses[count]); 268 byte[] expectedResponse = HceUtils.hexStringToBytes(mResponses[count]); 269 Log.d(TAG, HceUtils.getHexBytes("APDU response: ", response)); 270 271 if (response.length > expectedResponse.length) { 272 response = Arrays.copyOfRange(response, 273 response.length - expectedResponse.length, response.length); 274 } 275 276 if (!wildCard && !Arrays.equals(response, expectedResponse)) { 277 Log.d(TAG, "Unexpected APDU response: " 278 + HceUtils.getHexBytes("", response)); 279 return false; 280 } 281 return true; 282 } 283 284 @Override onItemSelected(AdapterView<?> parent, View view, int position, long id)285 public void onItemSelected(AdapterView<?> parent, View view, int position, 286 long id) { 287 if (position == 0) { 288 // Type-A 289 mAdapter.disableReaderMode(this); 290 mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A | 291 NfcAdapter.FLAG_READER_NFC_BARCODE | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null); 292 SharedPreferences.Editor editor = mPrefs.edit(); 293 editor.putBoolean("typeB", false); 294 editor.commit(); 295 } else { 296 // Type-B 297 mAdapter.disableReaderMode(this); 298 mAdapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_B | 299 NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null); 300 SharedPreferences.Editor editor = mPrefs.edit(); 301 editor.putBoolean("typeB", true); 302 editor.commit(); 303 } 304 } 305 306 @Override onNothingSelected(AdapterView<?> parent)307 public void onNothingSelected(AdapterView<?> parent) { 308 } 309 310 @Override getTestId()311 public String getTestId() { 312 return mLabel; 313 } 314 } 315