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