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