1 /* 2 * Copyright (C) 2011 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; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 import com.android.cts.verifier.nfc.tech.MifareUltralightTagTester; 22 import com.android.cts.verifier.nfc.tech.NdefTagTester; 23 import com.android.cts.verifier.nfc.tech.TagTester; 24 import com.android.cts.verifier.nfc.tech.TagVerifier; 25 import com.android.cts.verifier.nfc.tech.TagVerifier.Result; 26 27 import android.app.AlertDialog; 28 import android.app.Dialog; 29 import android.app.PendingIntent; 30 import android.app.ProgressDialog; 31 import android.content.DialogInterface; 32 import android.content.Intent; 33 import android.nfc.NfcAdapter; 34 import android.nfc.NfcManager; 35 import android.nfc.Tag; 36 import android.nfc.tech.MifareUltralight; 37 import android.nfc.tech.Ndef; 38 import android.os.AsyncTask; 39 import android.os.Bundle; 40 import android.util.Log; 41 import android.widget.ArrayAdapter; 42 import android.widget.TextView; 43 import android.widget.Toast; 44 45 /** 46 * Test activity for reading and writing NFC tags using different technologies. 47 * First, it asks the user to write some random data to the tag. Then it asks 48 * the user to scan that tag again to verify that the data was properly written 49 * and read back. 50 */ 51 public class TagVerifierActivity<T> extends PassFailButtons.ListActivity { 52 53 static final String TAG = TagVerifierActivity.class.getSimpleName(); 54 55 /** Non-optional argument specifying the tag technology to be used to read and write tags. */ 56 static final String EXTRA_TECH = "tech"; 57 58 private static final int NFC_NOT_ENABLED_DIALOG_ID = 1; 59 private static final int TESTABLE_TAG_DISCOVERED_DIALOG_ID = 2; 60 private static final int TESTABLE_TAG_REMINDER_DIALOG_ID = 3; 61 private static final int WRITE_PROGRESS_DIALOG_ID = 4; 62 private static final int READ_PROGRESS_DIALOG_ID = 5; 63 private static final int VERIFY_RESULT_DIALOG_ID = 6; 64 65 // Arguments used for the dialog showing what was written to the tag and read from the tag. 66 private static final String EXPECTED_CONTENT_ID = "expectedContent"; 67 private static final String ACTUAL_CONTENT_ID = "actualContent"; 68 private static final String IS_MATCH_ID = "isMatch"; 69 70 // The test activity has two states - writing data to a tag and then verifying it. 71 private static final int WRITE_STEP = 0; 72 private static final int VERIFY_STEP = 1; 73 74 private NfcAdapter mNfcAdapter; 75 private PendingIntent mPendingIntent; 76 private Class<?> mTechClass; 77 78 private int mStep; 79 private TagTester mTagTester; 80 private TagVerifier mTagVerifier; 81 private Tag mTag; 82 private ArrayAdapter<String> mTechListAdapter; 83 private TextView mEmptyText; 84 85 @Override onCreate(Bundle savedInstanceState)86 protected void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 setContentView(R.layout.nfc_tag); 89 setInfoResources(R.string.nfc_tag_verifier, R.string.nfc_tag_verifier_info, 0); 90 setPassFailButtonClickListeners(); 91 getPassButton().setEnabled(false); 92 93 parseIntentExtras(); 94 if (mTechClass != null) { 95 mTagTester = getTagTester(mTechClass); 96 97 mEmptyText = (TextView) findViewById(android.R.id.empty); 98 99 mTechListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1); 100 setListAdapter(mTechListAdapter); 101 102 NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE); 103 mNfcAdapter = nfcManager.getDefaultAdapter(); 104 mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()) 105 .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); 106 107 goToWriteStep(); 108 } else { 109 finish(); 110 } 111 } 112 parseIntentExtras()113 private void parseIntentExtras() { 114 try { 115 String tech = getIntent().getStringExtra(EXTRA_TECH); 116 if (tech != null) { 117 mTechClass = Class.forName(tech); 118 } 119 } catch (ClassNotFoundException e) { 120 Log.e(TAG, "Couldn't find tech for class", e); 121 } 122 } 123 124 @Override onResume()125 protected void onResume() { 126 super.onResume(); 127 if (!mNfcAdapter.isEnabled()) { 128 showDialog(NFC_NOT_ENABLED_DIALOG_ID); 129 } 130 131 mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); 132 } 133 134 @Override onPause()135 protected void onPause() { 136 super.onPause(); 137 mNfcAdapter.disableForegroundDispatch(this); 138 } 139 getTagTester(Class<?> techClass)140 private TagTester getTagTester(Class<?> techClass) { 141 if (Ndef.class.equals(techClass)) { 142 return new NdefTagTester(this); 143 } else if (MifareUltralight.class.equals(techClass)) { 144 return new MifareUltralightTagTester(); 145 } else { 146 throw new IllegalArgumentException("Unsupported technology: " + techClass); 147 } 148 } 149 150 @Override onNewIntent(Intent intent)151 protected void onNewIntent(Intent intent) { 152 Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); 153 if (tag != null) { 154 mTag = tag; 155 updateTechListAdapter(tag); 156 switch (mStep) { 157 case WRITE_STEP: 158 handleWriteStep(tag); 159 break; 160 161 case VERIFY_STEP: 162 handleVerifyStep(); 163 break; 164 } 165 } 166 } 167 handleWriteStep(Tag tag)168 private void handleWriteStep(Tag tag) { 169 if (mTagTester.isTestableTag(tag)) { 170 brutallyDismissDialog(TESTABLE_TAG_REMINDER_DIALOG_ID); 171 showDialog(TESTABLE_TAG_DISCOVERED_DIALOG_ID); 172 } else { 173 brutallyDismissDialog(TESTABLE_TAG_DISCOVERED_DIALOG_ID); 174 showDialog(TESTABLE_TAG_REMINDER_DIALOG_ID); 175 } 176 } 177 brutallyDismissDialog(int id)178 private void brutallyDismissDialog(int id) { 179 try { 180 dismissDialog(id); 181 } catch (IllegalArgumentException e) { 182 // Don't care if it hasn't been shown before... 183 } 184 } 185 handleVerifyStep()186 private void handleVerifyStep() { 187 new VerifyTagTask().execute(mTag); 188 } 189 updateTechListAdapter(Tag tag)190 private void updateTechListAdapter(Tag tag) { 191 mEmptyText.setText(R.string.nfc_no_tech); 192 String[] techList = tag.getTechList(); 193 mTechListAdapter.clear(); 194 for (String tech : techList) { 195 mTechListAdapter.add(tech); 196 } 197 } 198 199 class WriteTagTask extends AsyncTask<Tag, Void, TagVerifier> { 200 201 @Override onPreExecute()202 protected void onPreExecute() { 203 super.onPreExecute(); 204 showDialog(WRITE_PROGRESS_DIALOG_ID); 205 } 206 207 @Override doInBackground(Tag... tags)208 protected TagVerifier doInBackground(Tag... tags) { 209 try { 210 return mTagTester.writeTag(tags[0]); 211 } catch (Exception e) { 212 Log.e(TAG, "Error writing NFC tag...", e); 213 return null; 214 } 215 } 216 217 @Override onPostExecute(TagVerifier tagVerifier)218 protected void onPostExecute(TagVerifier tagVerifier) { 219 dismissDialog(WRITE_PROGRESS_DIALOG_ID); 220 mTagVerifier = tagVerifier; 221 if (tagVerifier != null) { 222 goToVerifyStep(); 223 } else { 224 Toast.makeText(TagVerifierActivity.this, R.string.nfc_writing_tag_error, 225 Toast.LENGTH_SHORT).show(); 226 goToWriteStep(); 227 } 228 } 229 } 230 goToWriteStep()231 private void goToWriteStep() { 232 mStep = WRITE_STEP; 233 mEmptyText.setText(getString(R.string.nfc_scan_tag, mTechClass.getSimpleName())); 234 mTechListAdapter.clear(); 235 } 236 goToVerifyStep()237 private void goToVerifyStep() { 238 mStep = VERIFY_STEP; 239 mEmptyText.setText(getString(R.string.nfc_scan_tag_again, mTechClass.getSimpleName())); 240 mTechListAdapter.clear(); 241 } 242 243 class VerifyTagTask extends AsyncTask<Tag, Void, Result> { 244 245 @Override onPreExecute()246 protected void onPreExecute() { 247 super.onPreExecute(); 248 showDialog(READ_PROGRESS_DIALOG_ID); 249 } 250 251 @Override doInBackground(Tag... tags)252 protected Result doInBackground(Tag... tags) { 253 try { 254 return mTagVerifier.verifyTag(tags[0]); 255 } catch (Exception e) { 256 Log.e(TAG, "Error verifying NFC tag...", e); 257 return null; 258 } 259 } 260 261 @Override onPostExecute(Result result)262 protected void onPostExecute(Result result) { 263 super.onPostExecute(result); 264 dismissDialog(READ_PROGRESS_DIALOG_ID); 265 mTagVerifier = null; 266 if (result != null) { 267 getPassButton().setEnabled(result.isMatch()); 268 269 Bundle args = new Bundle(); 270 args.putCharSequence(EXPECTED_CONTENT_ID, result.getExpectedContent()); 271 args.putCharSequence(ACTUAL_CONTENT_ID, result.getActualContent()); 272 args.putBoolean(IS_MATCH_ID, result.isMatch()); 273 showDialog(VERIFY_RESULT_DIALOG_ID, args); 274 275 goToWriteStep(); 276 } else { 277 Toast.makeText(TagVerifierActivity.this, R.string.nfc_reading_tag_error, 278 Toast.LENGTH_SHORT).show(); 279 goToWriteStep(); 280 } 281 } 282 } 283 284 @Override onCreateDialog(int id, Bundle args)285 public Dialog onCreateDialog(int id, Bundle args) { 286 switch (id) { 287 case NFC_NOT_ENABLED_DIALOG_ID: 288 return NfcDialogs.createNotEnabledDialog(this); 289 290 case TESTABLE_TAG_DISCOVERED_DIALOG_ID: 291 return createTestableTagDiscoveredDialog(); 292 293 case TESTABLE_TAG_REMINDER_DIALOG_ID: 294 return createTestableTagReminderDialog(); 295 296 case WRITE_PROGRESS_DIALOG_ID: 297 return createWriteProgressDialog(); 298 299 case READ_PROGRESS_DIALOG_ID: 300 return createReadProgressDialog(); 301 302 case VERIFY_RESULT_DIALOG_ID: 303 return createVerifyResultDialog(); 304 305 default: 306 return super.onCreateDialog(id, args); 307 } 308 } 309 createTestableTagDiscoveredDialog()310 private AlertDialog createTestableTagDiscoveredDialog() { 311 return new AlertDialog.Builder(this) 312 .setIcon(android.R.drawable.ic_dialog_info) 313 .setTitle(R.string.nfc_write_tag_title) 314 .setMessage(R.string.nfc_write_tag_message) 315 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 316 @Override 317 public void onClick(DialogInterface dialog, int which) { 318 new WriteTagTask().execute(mTag); 319 } 320 }) 321 .setOnCancelListener(new DialogInterface.OnCancelListener() { 322 @Override 323 public void onCancel(DialogInterface dialog) { 324 goToWriteStep(); 325 } 326 }) 327 .show(); 328 } 329 330 private AlertDialog createTestableTagReminderDialog() { 331 return new AlertDialog.Builder(this) 332 .setIcon(android.R.drawable.ic_dialog_alert) 333 .setTitle(R.string.nfc_wrong_tag_title) 334 .setMessage(getString(R.string.nfc_scan_tag, mTechClass.getSimpleName())) 335 .setPositiveButton(android.R.string.ok, null) 336 .show(); 337 } 338 339 private ProgressDialog createWriteProgressDialog() { 340 ProgressDialog dialog = new ProgressDialog(this); 341 dialog.setMessage(getString(R.string.nfc_writing_tag)); 342 return dialog; 343 } 344 345 private ProgressDialog createReadProgressDialog() { 346 ProgressDialog dialog = new ProgressDialog(this); 347 dialog.setMessage(getString(R.string.nfc_reading_tag)); 348 return dialog; 349 } 350 351 private AlertDialog createVerifyResultDialog() { 352 // Placeholder title and message that will be set properly in onPrepareDialog 353 return new AlertDialog.Builder(this) 354 .setIcon(android.R.drawable.ic_dialog_alert) 355 .setTitle(R.string.result_failure) 356 .setMessage("") 357 .setPositiveButton(android.R.string.ok, null) 358 .create(); 359 } 360 361 @Override 362 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { 363 switch (id) { 364 case VERIFY_RESULT_DIALOG_ID: 365 prepareVerifyResultDialog(dialog, args); 366 break; 367 368 default: 369 super.onPrepareDialog(id, dialog, args); 370 break; 371 } 372 } 373 374 private void prepareVerifyResultDialog(Dialog dialog, Bundle args) { 375 CharSequence expectedContent = args.getCharSequence(EXPECTED_CONTENT_ID); 376 CharSequence actualContent = args.getCharSequence(ACTUAL_CONTENT_ID); 377 boolean isMatch = args.getBoolean(IS_MATCH_ID); 378 379 AlertDialog alert = (AlertDialog) dialog; 380 alert.setTitle(isMatch 381 ? R.string.result_success 382 : R.string.result_failure); 383 alert.setMessage(getString(R.string.nfc_result_message, expectedContent, actualContent)); 384 } 385 386 @Override 387 public String getTestId() { 388 return getTagTestId(mTechClass); 389 } 390 391 static String getTagTestId(Class<?> primaryTech) { 392 return NfcTestActivity.class.getName() + "_" + primaryTech.getSimpleName(); 393 } 394 } 395