【问题标题】:Testing an app by faking NFC tag scan通过伪造 NFC 标签扫描来测试应用程序
【发布时间】:2016-01-19 16:38:26
【问题描述】:

我是 Android 新手,正在研究用于从 NFC 标签读取数据的近场通信。我既没有支持 NFC 的 Android 手机也没有 NFC 标签来测试我创建的应用程序。

我发现下面两篇文章说通过触发 Intent 来伪造 NFC 标签扫描。

  1. Possibility for Fake NFC(Near Field Communication) Launch

  2. Need To Fake an NFC Tag Being Scanned In Android

我根据第一篇文章更改了我的代码,点击一个按钮,我在第一个活动中触发了所需的 Intent。而我又创建了一个能够处理相同意图的活动。 NFC标签的读取和处理数据基于第二个活动的按钮点击。

问题是:每当我从第一个 Activity 触发假 NFC 标签扫描意图时,它都会抛出错误“No Activity found to handle Intent { act=android.nfc.action.NDEF_DISCOVERED (has extras ) }"。

Manifest 文件是这样的:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.expensemanager.saubhattacharya">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NFC"/>

<uses-feature android:name="android.hardware.nfc" android:required="false" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/icon1"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".Set_Monthly_Target"
        android:label="@string/title_activity_set__monthly__target"
        android:parentActivityName=".MainActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.expensemanager.saubhattacharya.MainActivity" />
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain"/>
            <data android:mimeType="image/*" />
        </intent-filter>
    </activity>
    <activity
        android:name=".Add_Daily_Expense"
        android:label="@string/add_daily_expense_activity"
        android:parentActivityName=".MainActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.expensemanager.saubhattacharya.MainActivity" />
        <intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="text/plain"/>
            <data android:mimeType="image/*" />
        </intent-filter>
    </activity>
</application>

</manifest>

第一个活动的意图触发代码sn-p如下:

public void scan_tag (View view)
{
    final Intent intent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, "Custom Messages");
    startActivity(intent);
}

处理上述触发器的第二个活动的代码 sn-p 如下:

public class Add_Daily_Expense extends AppCompatActivity {

Button read_data;
TextView show_data;
Tag detected_tag;
NfcAdapter nfcAdapter;
IntentFilter[] intentFilters;
public static final String MIME_TEXT_PLAIN = "text/plain";
public static final String MIME_IMAGE_ALL = "image/*";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add__daily__expense);
    final PackageManager pm = this.getPackageManager();
    show_data = (TextView) findViewById(R.id.show_data);
    nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
    read_data = (Button) findViewById(R.id.read_nfc);
    read_data.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC)) {
                try {
                    AlertDialog.Builder builder = new AlertDialog.Builder(Add_Daily_Expense.this);
                    builder.setMessage("NFC feature is not available on this device!")
                            .setCancelable(false)
                            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int id) {
                                    dialog.cancel();
                                }
                            });
                    AlertDialog alert = builder.create();
                    alert.show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                Toast.makeText(getApplicationContext(), "NFC feature is available on this device!", Toast.LENGTH_SHORT).show();
                HandleIntent(getIntent());
            }
        }
    });
}

public void HandleIntent(Intent intent)
{
    String action = intent.getAction();
    if(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action))
    {
        detected_tag = getIntent().getParcelableExtra(nfcAdapter.EXTRA_TAG);
        NDefReaderTask NDefReader = new NDefReaderTask();
        NDefReader.execute();
    }
}

public void onResume()
{
    super.onResume();
    if(nfcAdapter != null)
    setupForeGroundDispatch(this, nfcAdapter);
}

public void onPause()
{
    super.onPause();
    if(nfcAdapter != null)
    stopForeGroundDispatch(this, nfcAdapter);
}

public void onNewIntent(Intent intent)
{
    HandleIntent(intent);
}

public void setupForeGroundDispatch (final Activity activity, NfcAdapter nfcAdapter)
{
    Intent new_intent = new Intent(getApplicationContext(),Add_Daily_Expense.class);
    new_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

    PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),0,new_intent,0);
    intentFilters = new IntentFilter[1];
    String[][] techList = new String[][]{};

    intentFilters[0] = new IntentFilter();
    intentFilters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
    intentFilters[0].addCategory(Intent.CATEGORY_DEFAULT);
    try {
        intentFilters[0].addDataType(MIME_TEXT_PLAIN);
        intentFilters[0].addDataType(MIME_IMAGE_ALL);
    }
    catch(IntentFilter.MalformedMimeTypeException me)
    {
        me.printStackTrace();
    }

    nfcAdapter.enableForegroundDispatch(activity, pendingIntent, intentFilters, techList);
}

public void stopForeGroundDispatch (final Activity activity, NfcAdapter nfcAdapter)
{
    nfcAdapter.disableForegroundDispatch(activity);
}

public class NDefReaderTask extends AsyncTask <Tag, Void, String>
{
    @Override
    protected String doInBackground(Tag... params)
    {
        try
        {
            detected_tag = params[0];
            Ndef ndef = Ndef.get(detected_tag);
            ndef.connect();
            if(ndef != null)
            {
                NdefMessage ndefMessage = ndef.getCachedNdefMessage();
                NdefRecord[] records = ndefMessage.getRecords();
                for(NdefRecord ndefRecord : records)
                {
                    if((ndefRecord.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) || (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN))
                    {
                        byte[] payload = ndefRecord.getPayload();
                        String encoding1 = "UTF-8";
                        String encoding2 = "UTF-16";
                        String textEncoding = ((payload[0] & 128) == 0) ? encoding1 : encoding2;
                        int languageCodeLength = payload[0] & 0063;
                        return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
                    }
                }
            }
            ndef.close();
        }
        catch (UnsupportedEncodingException UE)
        {
            UE.printStackTrace();
        }
        catch (IOException IE)
        {
            IE.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPreExecute()
    {

    }

    protected void onPostExecute(String result)
    {
        if(result != null)
        {
            show_data.setText(result);
        }
    }
}
}

我的问题是:我在这里做错了什么?有没有其他方法可以通过伪造 NFC 标签扫描来测试我的应用程序?

【问题讨论】:

    标签: android android-intent mocking nfc ndef


    【解决方案1】:

    您为 NDEF_DISCOVERED 意图过滤器指定 MIME 类型过滤器:

    <data android:mimeType="text/plain"/>
    <data android:mimeType="image/*" />
    

    因此,伪造的 NFC Intent 需要包含其中一种 MIME 类型以匹配 Intent 过滤器。您可以使用setType() 方法将类型信息添加到意图:

    public void scan_tag (View view) {
        final Intent intent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED);
        intent.setType("text/plain");
        intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, ...);
        startActivity(intent);
    }
    

    另请注意,上述代码不会向 NFC 意图添加标签句柄。因此,您无法通过

    获得Tag 对象
    detected_tag = getIntent().getParcelableExtra(nfcAdapter.EXTRA_TAG);
    

    因此,您也无法使用以下方法获取Ndef 连接类的实例

    Ndef ndef = Ndef.get(detected_tag);
    

    您可能想查看以下有关模拟标签对象的问题/答案:

    最后,请注意您的代码中还有其他几个问题。

    【讨论】:

    • @Michael-- 非常感谢!我将通过你提到的要点,让你知道结果。是的,我假设我的代码中会有几个,因为我是新手,这就是为什么我想做适当的单元测试。 :)
    • @Michael-- 如果可能,请您大致告诉我代码中存在哪些问题?或者我在这种情况下的方法是错误的?我正在尝试同时进行一些单元测试。
    • 我能够通过仅将 MIME 类型保留为文本来解决此问题。我能够捕捉到意图。但由于我无法获得 NFC 适配器和标签信息,我无法测试整个代码。同时,如果可能,请突出显示我在代码中遇到的所有问题?
    • @SaumikBhattacharya 例如,一个问题是,您在 AsyncTask 中设置了 detected_tag = params[0];,但您从未将标签句柄作为参数传递给任务(而是直接使用它更新 detected_tag您稍后会再次用params[0] 覆盖)。另一种是在你的HandleIntent方法中使用getIntent()获取标签句柄(而不是使用参数intent)。
    • 这如何伪造 NFC 扫描 - 就像 OP 请求的那样?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-08
    • 2020-05-18
    • 1970-01-01
    • 1970-01-01
    • 2016-03-17
    • 1970-01-01
    相关资源
    最近更新 更多