android - 通过伪造 NFC 标签扫描来测试应用程序

标签 android android-intent mocking nfc ndef

我是 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

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

问题是:每当我从第一个 Activity 触发假 NFC 标签扫描 Intent 时,它都会抛出错误“找不到处理 Intent 的 Activity { act=android.nfc.action.NDEF_DISCOVERED (有额外的内容) ) }”。

list 文件如下所示:

<?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>

第一个 Activity 的 Intent 触发代码 fragment 如下:

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);
}

处理上述触发器的第二个 Activity 的代码 fragment 如下:

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 标签扫描来测试我的应用程序吗?

最佳答案

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

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

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

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 Intent 添加标签句柄。因此,您无法使用

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

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

Ndef ndef = Ndef.get(detected_tag);

您可能想研究以下有关模拟标记对象的问题/答案:

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

关于android - 通过伪造 NFC 标签扫描来测试应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34882171/

相关文章:

android - 大小为 0kb 的位图图像保存

java - Firebase DB 警告 : "Using an unspecified index. Consider adding ' ". indexOn"以获得更好的性能

java - startActivityForResult 返回错误的 Activity

c# - 使用 Moq 确定方法是否被调用

android - android-wheel 在三星 Galaxy S2 ICS 更新上工作吗

android - 在 Android 中设置 Crouton 消息的持续时间

java - Android:对另一个类中的 Activity 对象的引用变为 NULL

android - 将文本和应用程序名称发送到 Facebook

c# - 从模拟方法返回模拟

java - 如何使用 Java JUnit 和 Mockito 验证传递给静态函数的参数 (Spring Boot)