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