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